构造Lambda表达式以从字符串中获取嵌套属性。

54

我试图在运行时根据属性名称创建一个嵌套属性的lambda表达式。基本上,我正在尝试创建以下指定的lambda表达式:

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

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

使用以下类:

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

然而,我所获得的只有Foo类型和字符串"myBar.name"

如果它是一个普通属性,例如只需要值"myBar",那么我可以使用以下方法:

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

然而,这段代码不能处理嵌套属性,我不确定如何创建Lambda表达式来完成 foo.myBar.name 的工作。

我认为它应该是这样的:

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

但是我似乎无法弄清楚如何使所有东西都运作起来,或者是否有更好的方法在运行时完成这个任务。

3个回答

142

你的意思是:

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);
}
例如:
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
}

?


是的,这正是我正在寻找的,我的错误在于认为我需要在获取嵌套属性之前调用主体表达式(使用“Call”)。 - Seph
2
很棒的答案!+1 为清晰度 - ps2goat
Marc Gravell 给出了一个很棒的答案,解决了我的问题。另外,Resharper 对您的代码进行了优化:var param = Expression.Parameter(type, "x"); Expression body = propertyName.Split('.').Aggregate<string, Expression>(param, Expression.PropertyOrField); return Expression.Lambda(body, param); - The Senator
3
太棒了!Marc,是否有方法可以在嵌套列表中使用相同的行为?我的意思是类 Foo {public List<Bar> myBar {get;set;} }。 - Kate
@marc-gravell 是否有支持索引器(针对数组和列表)的更改?例如myBar.name[1].address[2] - HamedFathi
1
@HamedFathi 是的,但更棘手;你需要使用相关索引器访问器的方法信息的Expression.Call;请参见此处的 Expression.Call: https://sharplab.io/#v2:EYLgxg9gTgpgtADwGwBYA+ABATARgLABQGADAAQY4B0AMgJYB2AjpQKIIAOsAzl7RPVwDchNpxg8+9ADwYArDJzEANKQYAXFWAAWAQygA+faS6kAvKVIAKLitoBKM0a4BtWgF1hBIA== - 请注意,您不需要 LdMemberToken - 但您需要通过反射找到访问器。 - Marc Gravell

2

对于这个方法,propertyPath"Car", "Colour",对应于 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;
}

-1

要构建一个具有内联解决方案的lambda表达式,您可以执行以下操作:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接