2017-04-14 1 views
2

Ich habe eine Expression<Func<IMyclass, int>>, die ich in einen Setter wie Expression<Action<object, object>> typisiert konvertieren möchte. Wie kann das erreicht werden? Das Problem ist der object Parameter. Mit dem richtigen Typ (string) wäre das einfach.Create Setter Ausdruck <Action <Objekt, Objekt >> aus Ausdruck <Func <IM-Klasse, Int >>

class Foo 
{ 
    public Bar Bar { get; set; } 
} 
class Bar 
{ 
    public string Baz { get; set; }  
} 

static void Main(string[] args) 
{ 
    var expr = GetExpression(t => t.Bar.Baz); 
    var member = expr.Body as MemberExpression; 

    var p = Expression.Parameter(typeof(object), "p"); 
    // This does not work... 
    var assign = Expression.Assign(member, p); 
    var lambda = Expression.Lambda<Action<object, object>>(assign, p); 

    object o = new Foo(); 
    object v = "test"; 
    lambda.Compile().Invoke(o, v); 
} 

private static Expression<Func<Foo, string>> GetExpression(Expression<Func<Foo, string>> expr) 
{ 
    return expr; 
} 

Antwort

4

Es ist möglich, aber nicht ganz trivial. Zuerst müssen Sie Ihren ursprünglichen Ausdruck neu schreiben. Jetzt ist es diese Form hat:

(Foo t) => t.Bar.Baz; 

Sie müssen es so sein:

(object t) => ((Foo)t).Bar.Baz; 

Sie müssen also Ausdruck Besucher:

private class ReplaceParameterExpressionVisitor : System.Linq.Expressions.ExpressionVisitor { 
    private readonly ParameterExpression _toReplace; 
    private readonly ParameterExpression _replaceWith; 
    public ReplaceParameterExpressionVisitor(ParameterExpression toReplace, ParameterExpression replaceWith) { 
     _toReplace = toReplace; 
     _replaceWith = replaceWith; 
    }    

    protected override Expression VisitParameter(ParameterExpression node) { 
     if (node == _toReplace) 
      // replace with new parameter and convert to the old parameter type 
      return Expression.Convert(_replaceWith, _toReplace.Type); 
     return base.VisitParameter(node); 
    } 
} 

Dann wird Ihr Code wird:

static void Main(string[] args) 
{ 
    var expr = GetExpression(t => t.Bar.Baz); 
    var member = expr.Body as MemberExpression; 

    // define new parameter of type object 
    var target = Expression.Parameter(typeof(object), "t"); 
    var value = Expression.Parameter(typeof(object), "p"); 
    // replace old parameter of type Foo to new one 
    member = (MemberExpression) new ReplaceParameterExpressionVisitor(expr.Parameters[0], target).Visit(member); 
    // convert value to target type, because you cannot assign object to string 
    var assign = Expression.Assign(member, Expression.Convert(value, member.Type)); 
    // now we have (target, value) => ((Foo)target).Bar.Baz = (string) value; 
    var lambda = Expression.Lambda<Action<object, object>>(assign, target, value); 

    var o = new Foo(); 
    // set bar or will throw null reference 
    o.Bar = new Bar(); 
    object v = "test"; 
    lambda.Compile().Invoke(o, v); 
} 
+0

Schön! Kann dies ohne ExpressionVisitor gemacht werden? Oder wäre das komplizierter? – l33t

+1

Ja, das wird viel komplizierter, weil du den gleichen Besucher machen musst, der bereits für dich tut, aber du selbst. Ausdrucksbäume sind unveränderlich, Sie können also nicht einfach einige Teile davon ändern - Sie müssen einen neuen Baum erstellen. – Evk

+0

Ich denke, Sie sollten in der Lage sein, Ihren Besucher zu vereinfachen: Wechseln Sie zu 'Expression.Convert' in' VisitParameter' und entfernen Sie 'VisitMember'. – svick

Verwandte Themen