2011-01-13 7 views
2

Dies ist eine Lernübung in Ausdrucksbäumen.Wie füge ich eine Konvertierungsfunktion in meinen Ausdrucksbaum ein?

Ich habe diesen Arbeits Code:

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 
    } 
} 

gut funktioniert. Wie erwähnt, kopiert es die entsprechenden Eigenschaften von f zu b. Nun nehme ich an, dass ich eine Konvertierungs- oder Formatierungsmethode hinzufügen möchte, die die "from-Eigenschaft" übernimmt, einige Zauber ausführt und dann die Eigenschaft "to" gleich dem Ergebnis setzt. Beachten Sie die beiden auskommentierten Zeilen in der Mitte von CreateMapper.

Wie bewerkstellige ich das? Ich bin so weit gekommen, aber ich bin jetzt irgendwie verloren.

Antwort

3

Ihr Codebeispiel ist fast da; Sie können Expression.Call verwenden, um die Transformation durchzuführen, wie Sie es eindeutig versuchen. Statt toProp zum fromPropMember Zuordnung, können Sie zu einem Method zuweisen den Wert der Transformation darstellt.

Der schwierige Teil hier ist um herauszufinden, wie die Transformation zu tun, die ich für verschiedene Eigenschaften variieren nehmen.

Sie können die LINQ-Ausdruck mit ersetzen: (. Beachten Sie, dass die rechte Seite der Zuweisung jetzt fromTransformed ist eher als 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); 

wo GetTransform wie etwas aussieht (I‘ Hier wird angenommen, dass die Art der Transformation nur von der Art der Eigenschaft abhängt):

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"); 
} 

Dann müssen nur noch die Transformationen selbst ausgefüllt werden; zum Beispiel:

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

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

Dann würden Ihre verbrauchsTestMethode produzieren:

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

Sie ein Gentleman und ein Gelehrter sind. – Amy

+1

Err, oder eine Gentlewoman. – Amy

2

ich ein bisschen ein Spiel mit Ihrem Code hatte und ich denke, ich kann Ihnen ein schönes fließenden Stil Feld geben Kartenerbauer. Angesichts Ihrer Klassen Foo & Bar Sie diesen Code ausführen könnten:

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); 

Das Ergebnis ist, dass bar sieht nun, als ob es wie so erklärt wurden:

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

Das schöne an diesem Code ist, dass Sie don Sie müssen keine Reflektion im aufrufenden Code vornehmen, es werden Eigenschaftenarten abgeleitet, und Mapping-Eigenschaften verschiedener Typen werden sauber behandelt.

Hier ist der Code, der es tut:

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(); 
    } 
} 
+0

+1 Ausgezeichnet! Ich habe das Gefühl, dass ich irgendwann einen Nutzen dafür finden werde. – Ani

Verwandte Themen