2011-01-13 103 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(); 
    } 
}