53

I am trying to create a lambda expression for a nested property at run-time from the name of the propert. Basically I am trying to create the lambda expression specified by:

var expression = CreateExpression<Foo, object>(foo => foo.myBar.name);

private static Expression CreateExpression<TEntity, TReturn>(Expression<Func<TEntity, TReturn>> expression)
{
    return (expression as Expression);
}

With the classes:

class Foo
{
    public Bar myBar { get; set; }
}
class Bar
{
    public string name { get; set; }
}

However all I am given is the type of Foo and the string "myBar.name"

If it were a normal property such as just needing the value "myBar" then I could use the following:

private static LambdaExpression GetPropertyAccessLambda(Type type, string propertyName)
{
    ParameterExpression odataItParameter = Expression.Parameter(type, "$it");
    MemberExpression propertyAccess = Expression.Property(odataItParameter, propertyName);
    return Expression.Lambda(propertyAccess, odataItParameter);
}

However this code does not work for nested properties and I'm not sure how to create the LambdaExpression to do the work of foo.myBar.name.

I think it's going to be something like this:

GetExpression(Expression.Call(GetExpression(Foo, "myBar"), "name"))

But I can't seem to work out how to get it all working, or if there's a better way to do this at run-time.

abatishchev
  • 98,240
  • 88
  • 296
  • 433
Seph
  • 8,472
  • 10
  • 63
  • 94

3 Answers3

140

Do you mean:

static LambdaExpression CreateExpression(Type type, string propertyName) {
    var param = Expression.Parameter(type, "x");
    Expression body = param;
    foreach (var member in propertyName.Split('.')) {
        body = Expression.PropertyOrField(body, member);
    }
    return Expression.Lambda(body, param);
}

For example:

class Foo {
    public Bar myBar { get; set; }
}
class Bar {
    public string name { get; set; }
}
static void Main() {
    var expression = CreateExpression(typeof(Foo), "myBar.name");
    // x => x.myBar.name
}

?

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • Yep that's exactly what I was looking for, my mistake was thinking that I needed to invoke the body expression (using `Call`) before getting the nested property. – Seph Apr 25 '13 at 08:38
  • Fantastic answer from Marc Gravell which solved it for me. As a bonus, resharper did this to your code: var param = Expression.Parameter(type, "x"); Expression body = propertyName.Split('.').Aggregate(param, Expression.PropertyOrField); return Expression.Lambda(body, param); – The Senator Jun 18 '15 at 12:22
  • 3
    It is realy cool. Marc, are there any way to use the same behaviour with nested list? I mean class Foo {public List myBar { get; set; }} – Kate Aug 31 '15 at 13:27
  • @marc-gravell Any change to support indexers too (for arrays & lists)? like myBar.name[1].address[2] – HamedFathi Nov 09 '22 at 14:47
  • 1
    @HamedFathi yes, but trickier; you need to use `Expression.Call` with the method-info of the relevant indexer accessor; see `Expression.Call` here: https://sharplab.io/#v2:EYLgxg9gTgpgtADwGwBYA+ABATARgLABQGADAAQY4B0AMgJYB2AjpQKIIAOsAzl7RPVwDchNpxg8+9ADwYArDJzEANKQYAXFWAAWAQygA+faS6kAvKVIAKLitoBKM0a4BtWgF1hBIA== - note that you don't need the LdMemberToken - but you'd need to find the accessor via reflection – Marc Gravell Nov 10 '22 at 15:52
1

For this method, propertyPath is "Car", "Colour" for Driver.Car.Colour.


public static MemberExpression NestedProperty(Expression propertyHolder, params string[] propertyPath)
{
    MemberExpression memberExpression = Expression.Property(propertyHolder, propertyPath[0]);

    foreach (var member in propertyPath.Skip(1))
    {
        memberExpression = Expression.Property(memberExpression, member);
    }

    return memberExpression;
}
Zoidbergseasharp
  • 3,697
  • 1
  • 11
  • 20
-1

To construct a lambda expression but with an inline solution you can do:

var param = Expression.Parameter(typeOf(FooBar), "x");

// you "concat" your expression here :
var propertyExpression = Expression.PropertyOrField(param, "myBar");
propertyExpression = Expression.PropertyOrField(propertyExpression, "name");
// expected result : "x.myBar.name" as a body expression

var expression = Expression.Lambda(propertyExpression, param);
// x => x.myBar.name
Moff452
  • 131
  • 2
  • 8