我想动态创建以下表达式:
e.Collection.Select(inner => inner.Property)
我创建了此代码来执行此操作,但是执行表达式调用时遇到问题,有人知道我在做什么错吗?
private static Expression InnerSelect<TInnerModel>(IQueryable source, ParameterExpression externalParameter, string complexProperty)
{
// Creates the expression to the external property. // this generates: "e.Collection".
var externalPropertyExpression = Expression.Property(externalParameter, complexProperty);
// Creates the expression to the internal property. // this generates: "inner => inner.Property"
var innerParameter = Expression.Parameter(typeof(TInnerModel), "inner");
var innerPropertyExpression = Expression.Property(innerParameter, "Property");
var innerLambda = Expression.Lambda(innerPropertyExpression, innerParameter);
return Expression.Call(typeof(Queryable), "Select", new [] { typeof(TInnerModel) }, externalPropertyExpression, innerLambda);
}
错误:
类型'System.Linq.Queryable'上没有通用方法'Select'是 与提供的类型实参和实参兼容。没有类型 如果该方法是非泛型的,则应提供参数。
答案 0 :(得分:9)
因此,首先,主要问题非常简单。如错误消息所述,您尚未将足够的类型参数传递给Select。但是,当您解决此问题时,仍然会遇到问题,并且您很难看到和理解该问题。
让我们深入探讨。
您希望将其表示为表达式树:
e.Collection.Select(inner => inner.Property)
让我们先以其非扩展方法形式将其重写。
Queryable.Select<A, B>(e.Collection, inner => inner.Property)
其中A
是集合成员类型,而B
是Property
的类型。
现在,假设您在程序中有此表达式。 它在运行时实际上会做什么?它将为lambda构造一个表达式树,并将其传递给Queryable.Select。也就是说,它会执行以下操作:
var innerParameter = parameterFactory(whatever);
var lambdaBody = bodyFactory(whatever);
var lambda = makeALambda(lambdaBody, innerParameter);
Queryable.Select<TInnerModel>(e.Collection, lambda);
对吗?到目前为止,你和我在一起吗?
现在,假设我们希望将这个程序片段转换为一个表达式树,该树本身可以是lambda的主体。那应该是:
var theMethodInfoForSelect = whatever;
var receiverE = valueFactory(whatever);
var thePropertyInfoForCollection = whatever;
var theFirstArgument = propertyFactory(receiverE, thePropertyInfoForCollection);
...
再说一次,到目前为止,还有我吗?现在,关键问题是:第二个论点是什么?它不是传递的lambda
的值。记住,我们在这里正在构造一个表达式树,该树表示编译器为此生成的代码,而lambda中的表达式树不是 。您正在混合级别!
让我这样说:编译器期望“ 加一和三”。您正在通过 4 。那些是完全不同的东西!其中之一是如何获取号码的描述,而另一个是号码。您正在传递表达式树。编译器期望的是如何获取表达式树的描述。
那么:您现在是否需要编写代码来为lambda
的所有构造代码生成表达式树?谢天谢地我们为您提供了一种方便的方法,将表达式树变成如何生成表达式树的描述,这是Quote
操作。您需要使用它。
那么,构建表达式树所需的正确事件顺序是什么?让我们来看一下:
首先,您将需要拥有ParameterExpression
类型的e
。假设是:
ParameterExpression eParam = Expression.Parameter(typeof(E), "e");
接下来,您将需要Select
方法的方法信息。假设您可以正确地得到它。
MethodInfo selectMethod = whatever;
该方法需要两个参数,因此让我们制作一个参数表达式数组:
Expression[] arguments = new Expression[2];
您将需要Collection
属性的属性信息。我认为您可以做到:
MethodInfo collectionGetter = whatever;
现在我们可以构建属性表达式:
arguments[0] = Expression.Property(eParam, collectionGetter);
超级。接下来,我们需要开始构建该lambda。我们需要inner
的参数信息:
ParameterExpression innerParam = Expression.Parameter(typeof(Whatever), "inner");
我们需要Property
的属性信息,我认为您可以获取:
MethodInfo propertyGetter = whatever;
现在我们可以构建lambda的主体了:
MemberExpression body = Expression.Property(innerParam, propertyGetter);
lambda需要一个参数数组:
ParameterExpression[] innerParams = { innerParam };
根据主体和参数构建lambda:
var lambda = Expression.Lambda<Func<X, int>>(body, innerParams);
现在,您错过了这一步。 第二个参数是带引号的lambda,而不是lambda :
arguments[1] = Expression.Quote(lambda);
现在我们可以建立对Select的调用:
MethodCallExpression callSelect = Expression.Call(null, selectMethod, arguments);
我们完成了。
给某人一棵表情树,然后给他们一棵表情树一天;教他们如何自己找到表达树,他们可以一辈子做到这一点。我怎么这么快?
自从我编写了表达式树代码生成器以来,我立即对您可能遇到的问题有所了解。但是那是十年前的事,我并没有完全凭记忆做到这一点。我所做的是我编写了该程序:
using System;
using System.Linq.Expressions;
public interface IQ<T> {}
public class E
{
public IQ<X> C { get; set; }
}
public class X
{
public int P { get; set; }
}
public class Program
{
public static IQ<R> S<T, R>(IQ<T> q, Expression<Func<T, R>> f) { return null; }
public static void Main()
{
Expression<Func<E, IQ<int>>> f = e => S<X, int>(e.C, c => c.P);
}
}
现在,我想知道编译器为外部lambda的主体生成了什么代码,所以我去了https://sharplab.io/,粘贴了代码,然后单击Results-> Decompile C#,会将代码编译为IL,然后反编译为人类可读的C#。
这是我所知道的最好的方法,可以快速了解C#编译器在构建表达式树时的工作方式,而无论您是否知道编译器源代码是向前还是向后。这是一个非常方便的工具。