如何使用Select连接属性表达式和lambda?

Ren*_*ite 0 c# reflection

我想动态创建以下表达式:

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'与提供的类型参数和参数兼容.如果方法是非泛型的,则不应提供类型参数.

Eri*_*ert 9

所以,首先,主要问题非常简单.正如错误消息所示,您尚未向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#编译器在构建表达式树时所做的事情,无论您是否知道编译器源代码是向前还是向前.这是一个非常方便的工具.