Итак, во-первых, основная проблема очень проста.Как говорится в сообщении об ошибке, вы не передали достаточно аргументов типа для выбора.Но когда вы это исправите, у вас все еще будет проблема, и вам будет гораздо труднее увидеть и понять.
Давайте углубимся в это.
Вы хотите представить этокак дерево выражений:
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 # при построении дерева выражений, независимо от того,Вы знаете исходный код компилятора вперед и назад.Это очень удобный инструмент.