2017-04-14 100 views
2

我有一個Expression<Func<IMyclass, int>>,我想將其轉換爲類型爲Expression<Action<object, object>>的setter。這怎麼能實現?從Expression <Func <IMyclass,int >>創建setter表達式<Action <object,object >>

問題是object參數。使用正確的類型(string)這很容易。

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

回答

4

這是可能的,但不是很微不足道。首先你需要重寫你的原始表達式。現在,它有以下形式:

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

你需要的是這樣的:

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

所以你需要表達訪問者:

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

那麼你的代碼變成:

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

美麗!這可以在沒有ExpressionVisitor的情況下完成嗎?或者這會更復雜? – l33t

+1

是的,這將會複雜得多,因爲你必須真正做同樣的訪問者已經爲你做,但你自己。表達式樹是不可變的,所以你不能修改它的某些部分 - 你需要創建新的樹。 – Evk

+0

我認爲你應該能夠簡化你的訪問者:在'VisitParameter'中改變'Expression.Convert'並擺脫'VisitMember'。 – svick

相關問題