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

时间:2018-09-25 21:23:47

标签: c# reflection

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

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

1 个答案:

答案 0 :(得分:9)

因此,首先,主要问题非常简单。如错误消息所述,您尚未将足够的类型参数传递给Select。但是,当您解决此问题时,仍然会遇到问题,并且您很难看到和理解该问题。

让我们深入探讨。

您希望将其表示为表达式树:

e.Collection.Select(inner => inner.Property)

让我们先以其非扩展方法形式将其重写。

Queryable.Select<A, B>(e.Collection, inner => inner.Property)

其中A是集合成员类型,而BProperty的类型。

现在,假设您在程序中有此表达式。 它在运行时实际上会做什么?它将为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#编译器在构建表达式树时的工作方式,而无论您是否知道编译器源代码是向前还是向后。这是一个非常方便的工具。