我想动态创建以下表达式:
e.Collection.Select(inner => inner.Property)
Run Code Online (Sandbox Code Playgroud)
我创建了这个代码来执行它,但是当我执行表达式调用时,我有一个问题,有人知道我做错了什么?
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);
}
Run Code Online (Sandbox Code Playgroud)
错误:
类型'System.Linq.Queryable'上的通用方法'Select'与提供的类型参数和参数兼容.如果方法是非泛型的,则不应提供类型参数.
所以,首先,主要问题非常简单.正如错误消息所示,您尚未向Select传递足够的类型参数.但是当你解决这个问题时,你仍然会遇到问题,这个问题对你来说很难看到和理解.
让我们深入研究一下.
您希望将其表示为表达式树:
e.Collection.Select(inner => inner.Property)
Run Code Online (Sandbox Code Playgroud)
让我们从非扩展方法形式重写它.
Queryable.Select<A, B>(e.Collection, inner => inner.Property)
Run Code Online (Sandbox Code Playgroud)
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);
Run Code Online (Sandbox Code Playgroud)
对?你到目前为止和我在一起?
现在,假设我们希望将此程序片段转换为表达式树,该树本身可以是lambda的主体.那将是:
var theMethodInfoForSelect = whatever;
var receiverE = valueFactory(whatever);
var thePropertyInfoForCollection = whatever;
var theFirstArgument = propertyFactory(receiverE, thePropertyInfoForCollection);
...
Run Code Online (Sandbox Code Playgroud)
到目前为止,和我在一起?现在,关键问题是:第二个论点是什么?它不是lambda
你正在传递的价值.记住,我们在这里做的是构建一个表达式树,它表示编译器为该事物生成的代码,而lambda中的表达式树不是那个.你是混合水平!
让我这样说吧:编译器期待" 添加一个和三个 ".你通过了4.那些是非常不同的东西!其中一个是如何获得数字的描述,另一个是数字.您正在传递表达式树.编译器期望的是如何获取表达式树的描述.
所以:你现在必须编写为所有lambda
构造代码生成表达式树的代码吗?谢天谢地,不.我们为您提供了一种方便的方法,可以将表达式树转换为如何生成表达式树的描述,这是Quote
操作.你需要使用它.
那么,构建表达式树需要做的事件的正确顺序是什么?让我们来看看它:
首先,您需要一种已经拥有ParameterExpression
的类型e
.我们假设是:
ParameterExpression eParam = Expression.Parameter(typeof(E), "e");
Run Code Online (Sandbox Code Playgroud)
接下来,您将需要该方法的方法信息Select
.让我们假设你可以正确地得到它.
MethodInfo selectMethod = whatever;
Run Code Online (Sandbox Code Playgroud)
该方法有两个参数,所以让我们创建一个参数表达式数组:
Expression[] arguments = new Expression[2];
Run Code Online (Sandbox Code Playgroud)
您需要有关房产的房产信息Collection
.我假设你可以得到:
MethodInfo collectionGetter = whatever;
Run Code Online (Sandbox Code Playgroud)
现在我们可以构建属性表达式:
arguments[0] = Expression.Property(eParam, collectionGetter);
Run Code Online (Sandbox Code Playgroud)
超.接下来我们需要开始构建lambda.我们需要一个参数信息inner
:
ParameterExpression innerParam = Expression.Parameter(typeof(Whatever), "inner");
Run Code Online (Sandbox Code Playgroud)
我们需要一个属性信息Property
,我假设你可以得到:
MethodInfo propertyGetter = whatever;
Run Code Online (Sandbox Code Playgroud)
现在我们可以构建lambda的主体:
MemberExpression body = Expression.Property(innerParam, propertyGetter);
Run Code Online (Sandbox Code Playgroud)
lambda采用一系列参数:
ParameterExpression[] innerParams = { innerParam };
Run Code Online (Sandbox Code Playgroud)
从body和参数构建lambda:
var lambda = Expression.Lambda<Func<X, int>>(body, innerParams);
Run Code Online (Sandbox Code Playgroud)
现在你错过了一步.第二个参数是引用的lambda,而不是lambda:
arguments[1] = Expression.Quote(lambda);
Run Code Online (Sandbox Code Playgroud)
现在我们可以构建对Select的调用:
MethodCallExpression callSelect = Expression.Call(null, selectMethod, arguments);
Run Code Online (Sandbox Code Playgroud)
我们已经完成了.
给某人一个表达树,你给他们一天的表达树; 教他们如何自己找到表达树,他们可以一辈子去做.我怎么这么快就这么做?
自从我编写表达式树代码生成器以来,我对您可能遇到的问题有了一些直接的了解.但那是十年前的事了,我并没有完全从记忆中做到这一点.我做的是我写了这个程序:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
现在我想知道编译器为外部lambda的主体生成了什么代码,所以我去了https://sharplab.io/,粘贴在代码中,然后点击Results - > Decompile C#,将代码编译为IL,然后将其反编译回人类可读的C#.
这是我所知道的最好的方法,可以快速了解C#编译器在构建表达式树时所做的事情,无论您是否知道编译器源代码是向前还是向前.这是一个非常方便的工具.