tags:

views:

63

answers:

1

I'm generating an interface to concrete implementation copier. A later step will be to determine if I can easily add varying behaviors depending on the copy type requested (straight copy, or try, or try-catch-addToValidationDictionary). This is the main one I need/am working on is the try-catch-addToValidationDictionary. It would be lovely if the copy statements themselves(result.AssetTag = asset.AssetTag) were reusable in list form for another consumer that doesn't need the try/catch/validation functionality.

The General form is this:

public static AssetService
{
public static ModelAsset CreateAssetDomain(IAmAnAsset asset, IValidationDictionary validationDictionary)
 {

 var result=new ModelAsset();
 var hasExceptions=false;
  try
        {
            result.AssetTag = asset.AssetTag;
        }
        catch (System.Exception exception)
        {
            validationDictionary.AddError(Member.Name<IAmAnAsset>(lIAmAnAsset => lIAmAnAsset.AssetTag), exception.Message);
            hasExceptions = true;
        }
 try
        {
            result.LocationIdentifer = asset.LocationIdentifer;
        }
        catch (System.Exception exception)
        {
            validationDictionary.AddError(Member.Name<IAmAnAsset>(lIAmAnAsset => lIAmAnAsset.LocationIdentifer), exception.Message);
            hasExceptions = true;
        }
  ...
  if (hasExceptions)
    throw new ArgumentException("Failed validation");

 return result;
 }
}

I'm trying to factor out some of the repetition with lambdas but the Member.Name<IAmAnAsset>(lIAmAnAsset => lIAmAnAsset.AssetTag) from this post seems to only take an Expression<Func<T,object>> and I'm not sure how you would make use of the Expression> overload.

One attempt was as follows:

 Action<Action, Expression<Func<IAmAnAsset, object>>> CopyActions = (copyAction, expression) =>
            {
                try
                {
                    copyAction();
                }
                catch (Exception exception)
                {

                    validationDictionary.AddError(Member.Name<IAmAnAsset>(expression), exception.Message);
                    hasExceptions = true;
                }
            };

  var copyActions = new Dictionary<string,Action>()
    {
    Member.Name<IAmAnAsset>(z=>z.AddedBy),()=>result.AddedBy=asset.AddedBy},
    Member.Name<IAmAnAsset>(z=>z.AssetTag),()=>result.AssetTag=asset.AssetTag},
    ...
    }
 foreach (var item in copyActions)
        {
            tryCopyAction(item.Value, item.Key);
        }
 if (hasExceptions)
throw new ArgumentException("Failed validation");

 return result;

I'm hoping for a solution that reduces the duplication inherent in Member.Name<IAmAnAsset>(z=>z.AddedBy),()=>result.AddedBy=asset.AddedBy}, on any of the following criteria:

  • needing the IAmAnAsset.AddedBy in 2 places on each line
  • needing .AddedBy 3 times on the same line
  • Member.Name<IAmAnAsset> on each and every line
    • Is it possible to have this Expression utilized to retrieve either the string name, or the value of evaluating it?
A: 

How about a simple function?

public static class Member
{
  private static string GetMemberName(Expression expression)
  {
    switch (expression.NodeType)
    {
      case ExpressionType.MemberAccess: var memberExpression = (MemberExpression)expression; var supername = GetMemberName(memberExpression.Expression); if (String.IsNullOrEmpty(supername)) return memberExpression.Member.Name; return String.Concat(supername, '.', memberExpression.Member.Name);
      case ExpressionType.Call: var callExpression = (MethodCallExpression)expression; return callExpression.Method.Name;
      case ExpressionType.Convert: var unaryExpression = (UnaryExpression)expression; return GetMemberName(unaryExpression.Operand);
      case ExpressionType.Parameter: return String.Empty;
      default: throw new ArgumentException("The expression is not a member access or method call expression");
    }
  }
  public static string Name<T,V>(Expression<Func<T, V>> expression)
  {
    return GetMemberName(expression.Body);
  }
  public static string Name<T>(Expression<Action<T>> expression)
  {
    return GetMemberName(expression.Body);
  }
}

void Copy<D, S, V>(D dest, S source, Expression<Func<S, V>> getVal, Action<D, V> setVal, IDictionary validationDictionary)
{
  Func<S, V> doGetVal = getVal.Compile();
  try { setVal(dest, (V)doGetVal(source)); }
  catch (System.Exception exception)
  {
    validationDictionary.Add(Member.Name<S,V>(getVal), exception.Message);
  }
}

class TestAsset { public string AssetTag { get; set; } public string LocationIdentifier { get; set; } }
TestAsset Test()
{
  Dictionary<string, string> validationDictionary = new Dictionary<string, string>();
  var result = new TestAsset{ AssetTag = "a", LocationIdentifier = "b" };
  var asset = new TestAsset{ AssetTag = "A", LocationIdentifier = "B" };
  var validationCount = validationDictionary.Count(); 
  Copy(result, asset, x => asset.AssetTag, (x, v) => x.AssetTag = v, validationDictionary); 
  Copy(result, asset, x => asset.LocationIdentifier, (x, v) => x.LocationIdentifier = v, validationDictionary); 
  if (validationCount < validationDictionary.Count) throw new ArgumentException("Failed validation"); 
  return result;
}
John Fisher
The Member.Name is a way of achieving compile time property name safety. So instead of having a string literal "AssetTag", I have a lambda expression utilizing that property that can retrieve the string without reflection or string literals that are bug prone.
Maslow
Also your function doesn't account for the variance of the Member.Name argument being passed, and makes the usage much uglier. Action<S> doesn't return anything so it can't be used for the SetVal. Since that argument would return different types another type argument would make the usage uglier, and unlikely to be inferrable.
Maslow
I've fixed the function declaration, and commented on a different way to avoid reflection and string literals.
John Fisher
constants don't avoid the problem that the property is ever renamed and subtle bugs are introduced, the Lambda expression method is compile time safe. Additionally that would require adding constants for every property on every class this functionality would be used for.
Maslow
I've rewritten the answer with code that worked when I tested it.
John Fisher
I'm surprised the copy Function was able to infer the type parameters, looks pretty smooth.
Maslow