Как объединить выражение свойства и лямбду, используя Select? - PullRequest
0 голосов
/ 26 сентября 2018

Я хотел бы динамически создать следующее выражение:

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», совместимого с предоставленными аргументами и аргументами типа.Аргументы типа не должны предоставляться, если метод не является универсальным.

1 Ответ

0 голосов
/ 26 сентября 2018

Итак, во-первых, основная проблема очень проста.Как говорится в сообщении об ошибке, вы не передали достаточно аргументов типа для выбора.Но когда вы это исправите, у вас все еще будет проблема, и вам будет гораздо труднее увидеть и понять.

Давайте углубимся в это.

Вы хотите представить этокак дерево выражений:

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

Давайте начнем с переписывания его в форме, не относящейся к расширению.

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

Где A - тип члена коллекции, а Bэто тип Property.

Теперь предположим, что у вас есть это выражение в вашей программе. Что бы он на самом деле делал во время выполнения? Он построил бы дерево выражений для лямбды и передал бы его в Queryable.Select.То есть это будет что-то вроде:

var innerParameter = parameterFactory(whatever);
var lambdaBody = bodyFactory(whatever);
var lambda = makeALambda(lambdaBody, innerParameter);
Queryable.Select<TInnerModel>(e.Collection, lambda);

Верно?Вы со мной?

Теперь предположим, что мы хотим перевести этот фрагмент программы в дерево выражений, которое само может быть телом лямбды.Это было бы:

var theMethodInfoForSelect = whatever;
var receiverE = valueFactory(whatever);
var thePropertyInfoForCollection = whatever;
var theFirstArgument = propertyFactory(receiverE, thePropertyInfoForCollection);
...

Опять со мной так далеко?Теперь ключевой вопрос: каков второй аргумент ?Это НЕ значение 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);

Super.Далее нам нужно начать строить эту лямбду.Нам нужна информация о параметрах для inner:

    ParameterExpression innerParam = Expression.Parameter(typeof(Whatever), "inner");

Нам понадобится информация о свойствах для Property, которую, я полагаю, вы можете получить:

    MethodInfo propertyGetter = whatever;

Теперь мы можемсборка тела лямбды:

    MemberExpression body = Expression.Property(innerParam, propertyGetter);

лямбда принимает массив параметров:

    ParameterExpression[] innerParams = { innerParam };

сборка лямбды из тела и параметров:

    var lambda = Expression.Lambda<Func<X, int>>(body, innerParams);

Теперь шаг, который вы пропустили. Второй аргумент - это лямбда в кавычках, а не лямбда :

    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);
    }
}

Теперь я хотел знать, какой код был сгенерирован компилятором для тела внешней лямбды, поэтому я перешел к https://sharplab.io/,, вставленному в код, и затем щелкнул Результаты -> Декомпилировать C #, которыйскомпилирует код в IL, а затем декомпилирует его обратно в читаемый человеком C #.

Это лучший из известных мне способов быстрого понимания того, что делает компилятор C # при построении дерева выражений, независимо от того,Вы знаете исходный код компилятора вперед и назад.Это очень удобный инструмент.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...