无法使用 List<T> 手动创建 Lambda 表达式树

Unable to manually create Lambda expression tree with List<T>

我需要帮助手动创建此 lambda 表达式。我无法通过正确创建 MemberExpression 来隔离底层 List<Dog> 的“Breed”属性。在这个例子中,BreedDog 的 属性。

这是我需要手动创建的lambda:

int maxlen = dogList.Select(d => d.Breed.Trim().Length).OrderByDescending(d1 => d1).First();

这是我尝试获取 Breed 属性:

ParameterExpression dogParam = Expression.Parameter(typeof(List<Dog>), "dog");

MemberExpression dogMember = 
    Expression.Field(dogParam, dogMember.Type.GetGenericTypeDefinition().GetProperty("Breed"));

我不确定你的问题到底出在哪里,但它已经帮助你找到你最可能需要的 API:

您可以使用 Expression.Lambda 创建一个 lambda 表达式作为 Select 的参数。在 lambda 表达式中,您需要 Expression.MakeMemberAccess 才能访问 Breed 属性。您需要反射方法(例如 Type.GetMember)来获取相关的 MemberInfo 对象。

这是一个痛苦,一个真正的痛苦

首先注意你可以作弊,看看编译器生成了什么:

Expression<Func<List<Dog>, int>> exp = dogList => dogList.Select(d => d.Breed.Trim().Length).OrderByDescending(d1 => d1).First();

遗憾的是,这并不是一个很大的帮助...您有两个“内部”lambda 表达式(SelectOrderByDescending)和三个通用方法(Select , OrderByDescending, First)...一个痛苦.

我已经注释了所有的代码行,所以应该很清楚每行的作用。

// The dogList parameter
ParameterExpression dogParam = Expression.Parameter(typeof(List<Dog>), "dog");

// Begin of inner Select expression

// d parameter
ParameterExpression dParam = Expression.Parameter(typeof(Dog), "d");

// d.Breed *property* access (if it is a *field*, use Expression.Field)
MemberExpression dogProperty = Expression.Property(dParam, nameof(Dog.Breed));

// d.Breed.Trim() method call
MethodCallExpression trimCall = Expression.Call(dogProperty, nameof(string.Trim), Type.EmptyTypes);

// d.Breed.Trim().Length property access
MemberExpression lengthProperty = Expression.Property(trimCall, nameof(string.Length));

// d => d.Breed.Trim().Length
Expression<Func<Dog, int>> selectExpression = Expression.Lambda<Func<Dog, int>>(lengthProperty, dParam);

// End of inner Select expression

// Find the "right" overload of Enumerable.Select... A pain.
MethodInfo selectTSourceTResult = (from x in typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)
                                    where x.Name == nameof(Enumerable.Select)
                                    let args = x.GetGenericArguments()
                                    where args.Length == 2
                                    let pars = x.GetParameters()
                                    where pars.Length == 2 &&
                                        pars[0].ParameterType == typeof(IEnumerable<>).MakeGenericType(args[0]) &&
                                        pars[1].ParameterType == typeof(Func<,>).MakeGenericType(args[0], args[1])
                                    select x).Single();

// Make the generic overload of Enumerable.Select "specific" for using with Func<Dog, int>
MethodInfo selectDogInt32 = selectTSourceTResult.MakeGenericMethod(typeof(Dog), typeof(int));

// Note that Enumerable.Select is a static method, the first parameter is the IEnumerable, the second is the Func<,>
// Enumerable.Select(dogList, d => d.Breed.Trim().Length)
MethodCallExpression selectCall = Expression.Call(selectDogInt32, dogParam, selectExpression);

// Begin of inner OrderByDescending expression

// d1 parameter
ParameterExpression d1Param = Expression.Parameter(typeof(int), "d1");

// d1 => d1
Expression<Func<int, int>> orderByExpression = Expression.Lambda<Func<int, int>>(d1Param, d1Param);

// End of inner OrderByDescending expression

// Find the "right" overload of Enumerable.OrderByDescending... Another pain.
MethodInfo orderByDescendingTSourceTKey = (from x in typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)
                                            where x.Name == nameof(Enumerable.OrderByDescending)
                                            let args = x.GetGenericArguments()
                                            where args.Length == 2
                                            let pars = x.GetParameters()
                                            where pars.Length == 2 &&
                                                pars[0].ParameterType == typeof(IEnumerable<>).MakeGenericType(args[0]) &&
                                                pars[1].ParameterType == typeof(Func<,>).MakeGenericType(args[0], args[1])
                                            select x).Single();

// Make the generic overload of Enumerable.OrderByDescending "specific" for using with Func<int, int>
MethodInfo orderByDescendingInt32Int32 = orderByDescendingTSourceTKey.MakeGenericMethod(typeof(int), typeof(int));

// Note that Enumerable.OrderByDescending is a static method, the first parameter is the IEnumerable, the second is the Func<,>
// Enumerable.OrderByDescending(Enumerable.Select(...), d1 => d1)
MethodCallExpression orderByDescendingCall = Expression.Call(orderByDescendingInt32Int32, selectCall, orderByExpression);

// Find the "right" overload of Enumerable.First... Third big pain.
MethodInfo firstTSource = (from x in typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)
                            where x.Name == nameof(Enumerable.First)
                            let args = x.GetGenericArguments()
                            where args.Length == 1
                            let pars = x.GetParameters()
                            where pars.Length == 1 &&
                                pars[0].ParameterType == typeof(IEnumerable<>).MakeGenericType(args[0])
                            select x).Single();

// Make the generic overload of Enumerable.OrderByDescending "specific" for using with int
MethodInfo firstInt32 = firstTSource.MakeGenericMethod(typeof(int));

// Note that Enumerable.First is a static method, the first parameter is the IEnumerable
// Enumerable.First(Enumerable.OrderByDescending(...))
MethodCallExpression firstCall = Expression.Call(firstInt32, orderByDescendingCall);