Search code examples
c#lambdaexpression-trees

Create setter Expression<Action<object, object>> from Expression<Func<IMyclass, int>>


I have an Expression<Func<IMyclass, int>> which I want to convert to a setter typed as Expression<Action<object, object>>. How can that be achieved?

The problem is the object parameter. With the correct type (string) this would be easy.

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

Solution

  • It's possible but not quite trivial. First you need to rewrite your original expresion. Now it has this form:

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

    You need it to be like this:

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

    So you need expression visitor:

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

    Then your code becomes:

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