2011-01-13 90 views
2

这是一个表达树的学习练习。如何在我的表达式树中插入转换函数?

我有这样的工作代码:

class Foo 
{ 
    public int A { get; set; } 
    public string B { get; set; } 
} 

class Bar 
{ 
    public int C { get; set;} 
    public string D { get; set; } 
} 

class FieldMap 
{ 
    public PropertyInfo From { get; set; } 
    public PropertyInfo To { get; set; } 

} 

class Program 
{ 
    static Action<TFrom, TTo> CreateMapper<TFrom, TTo>(IEnumerable<FieldMap> fields) 
    { 
     ParameterExpression fromParm = Expression.Parameter(typeof(TFrom), "from"); 
     ParameterExpression toParm = Expression.Parameter(typeof(TTo), "to"); 

     //var test = new Func<string, string>(x => x); 
     //var conversionExpression = Expression.Call(null, test.Method); 

     var assignments = from fm in fields 
          let fromProp = Expression.Property(fromParm, fm.From) 
          let toProp = Expression.Property(toParm, fm.To) 
          select Expression.Assign(toProp, fromProp); 

     var lambda = Expression.Lambda<Action<TFrom, TTo>>(
      Expression.Block(assignments), 
      new ParameterExpression[] { fromParm, toParm }); 

     return lambda.Compile(); 
    } 

    static void Main(string[] args) 
    { 
     var pa = typeof(Foo).GetProperty("A"); 
     var pb = typeof(Foo).GetProperty("B"); 
     var pc = typeof(Bar).GetProperty("C"); 
     var pd = typeof(Bar).GetProperty("D"); 

     var mapper = CreateMapper<Foo, Bar>(new FieldMap[] 
     { 
      new FieldMap() { From = pa, To = pc }, 
      new FieldMap() { From = pb, To = pd } 
     }); 

     Foo f = new Foo(); 
     Bar b = new Bar(); 

     f.A = 20; 
     f.B = "jason"; 
     b.C = 25; 
     b.D = "matt"; 

     mapper(f, b);  // copies properties from f into b 
    } 
} 

很好地工作。如前所述,它将相应的属性从f复制到b。现在,假设我想添加一些采用“from属性”的转换或格式化方法,做了一些魔术,然后将“to property”设置为等于结果。请注意0​​中间的两条注释掉的行。

我该如何做到这一点?我得到了这一点,但我现在有点失落。

回答

3

你的代码示例几乎就在那里;您可以使用Expression.Call进行转换,正如您明确要做的那样。代替将toProp分配给fromProp成员表达式,您可以分配给代表转换值的MethodCallExpression

这里比较棘手的部分是找出如何来做转换,我认为这将是不同的属性。

您可以替换与LINQ表达式:(注意分配的右手边是现在fromTransformed而非fromProp

var assignments = from fm in fields 
        let fromProp = Expression.Property(fromParm, fm.From) 
        let fromPropType = fm.From.PropertyType 
        let fromTransformed 
         = Expression.Call(GetTransform(fromPropType), fromProp) 
        let toProp = Expression.Property(toParm, fm.To) 
        select Expression.Assign(toProp, fromTransformed); 

其中GetTransform看起来像(I”此处假设转换的性质仅取决于属性的类型):

private static MethodInfo GetTransform(Type type) 
{ 
    return typeof(Program).GetMethod(GetTransformName(type)); 
} 

private static string GetTransformName(Type type) 
{ 
    if (type == typeof(int)) 
     return "MapInt"; 

    if (type == typeof(string)) 
     return "MapString"; 

    throw new ArgumentException("Unknown type"); 
} 

然后唯一要做的就是自己填充转换;例如:

public static int MapInt(int x) { return x * 2; } 

public static string MapString(string x) { return x + x; } 

然后,您使用的测试方法会产生:

b.c == 40 
b.d == "jasonjason" 
+1

你是一个绅士和学者。 – Amy 2011-01-13 03:32:07

+1

呃,还是一位女士。 – Amy 2011-01-13 03:33:07

2

我有一点与您的代码一出戏,我想我可以给你一个很好的流畅风格的场地图生成器。鉴于你的类Foo & Bar你可以运行此代码:

var foo = new Foo() { A = 20, B = "jason", }; 
var bar = new Bar() { C = 25, D = "matt", }; 

var fm = new FieldMapBuilder<Foo, Bar>() 
    .AddMap(f => f.A, b => b.C) 
    .AddMap(f => f.B, b => b.D) 
    .AddMap(f => f.A, b => b.D, x => String.Format("!{0}!", x)) 
    .Compile(); 

fm(foo, bar); 

结果是bar现在看起来好像被宣布像这样:

var bar = new Bar() { C = 20, D = "!20!", }; 

这段代码的好处是你不不需要在调用代码中进行任何反射,就可以推断出属性类型,并且它可以巧妙地处理不同类型的映射属性。

下面是做它的代码:

public class FieldMapBuilder<TFrom, TTo> 
{ 
    private Expression<Action<TFrom, TTo>>[] _fieldMaps = null; 

    public FieldMapBuilder() 
    { 
     _fieldMaps = new Expression<Action<TFrom, TTo>>[] { }; 
    } 

    public FieldMapBuilder(Expression<Action<TFrom, TTo>>[] fieldMaps) 
    { 
     _fieldMaps = fieldMaps; 
    } 

    public FieldMapBuilder<TFrom, TTo> AddMap<P>(
     Expression<Func<TFrom, P>> source, 
     Expression<Func<TTo, P>> destination) 
    { 
     return this.AddMap<P, P>(source, destination, x => x); 
    } 

    public FieldMapBuilder<TFrom, TTo> AddMap<PFrom, PTo>(
     Expression<Func<TFrom, PFrom>> source, 
     Expression<Func<TTo, PTo>> destination, 
     Expression<Func<PFrom, PTo>> map) 
    { 
     var paramFrom = Expression.Parameter(typeof(TFrom), "from"); 
     var paramTo = Expression.Parameter(typeof(TTo), "to"); 

     var invokeExpressionFrom = 
       Expression.Invoke(map, Expression.Invoke(source, paramFrom)); 

     var propertyExpressionTo = 
       Expression.Property(paramTo, 
      (destination.Body as MemberExpression).Member as PropertyInfo); 

     var assignmentExpression = 
       Expression.Assign(propertyExpressionTo, invokeExpressionFrom); 

     return new FieldMapBuilder<TFrom, TTo>(
       _fieldMaps.Concat(new Expression<Action<TFrom, TTo>>[] 
       { 
        Expression.Lambda<Action<TFrom, TTo>>(
         assignmentExpression, 
         paramFrom, 
         paramTo) 
       }).ToArray()); 
    } 

    public Action<TFrom, TTo> Compile() 
    { 
     var paramFrom = Expression.Parameter(typeof(TFrom), "from"); 
     var paramTo = Expression.Parameter(typeof(TTo), "to"); 

     var expressionBlock = 
      Expression.Block(_fieldMaps 
       .Select(fm => Expression.Invoke(fm, paramFrom, paramTo)) 
       .ToArray()); 

     var lambda = Expression.Lambda<Action<TFrom, TTo>>(
      expressionBlock, 
      paramFrom, 
      paramTo); 

     return lambda.Compile(); 
    } 
}