Search code examples
c#.netlinqexpression-trees

How to bind nested members using Expression.Bind / Expression.MemberBind?


Test classes

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

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

public class BindFoo
{
    public string BarBaz { get; set; }
}

Snippet

Expression<Func<Foo, object>> baz = x => x.Bar.Baz;
var param = Expression.Parameter(typeof(Foo), "x");

var bindings = typeof(BindFoo)
                    .GetProperties()
                    .Select(x => Expression.Bind(x, (MemberExpression)baz.Body))
                    .OfType<MemberBinding>()
                    .ToArray();

                var expression = Expression.Lambda<Func<Foo, object>>(
                        Expression.MemberInit(
                            Expression.New(typeof(BindFoo).GetConstructor(Type.EmptyTypes)),
                            bindings),
                    param);

                var func = expression.Compile();

Throws the 'x' not defined error at expression.Compile() when the property is nested. How do I bind the nested property Bar.Baz?

The expression built in the code above is x => new BindFoo() {BarBaz = x.Bar.Baz}, which is what I want but I think the x.Bar.Baz had not been bound correctly.


Solution

  • The problem is not related to Bind and nested members, but the parameter of the dynamically created lambda expression.

    Parameters bind by instance, not by name. Here

    Expression<Func<Foo, object>> baz = x => x.Bar.Baz;
    var param = Expression.Parameter(typeof(Foo), "x");
    

    you defined a new parameter, but then trying to use baz.Body which is bound to it's own parameter.

    The solution is to use the original parameter

    Expression<Func<Foo, object>> baz = x => x.Bar.Baz;
    var param = baz.Parameters[0];
    

    or replace the baz.Parameters[0] with the new parameter using expression visitor.