Зачем вам цитировать LambdaExpression?
Я прочитал этот ответ и понял из него конкретный случай, который он выделяет: когда у вас есть лямбда в другой лямбде, и вы не хотите, чтобы внутренняя лямбда случайно компилировалась с внешней. Когда внешнее компилируется, вы хотите, чтобы внутреннее лямбда-выражение оставалось деревом выражений. Там, да, имеет смысл процитировать внутреннее лямбда-выражение.
Но это все, я верю. Есть ли другой вариант использования для цитирования лямбда-выражения?
А если нет, то почему все операторы LINQ, т.е. расширения на IQueryable<T>
которые объявлены в Queryable
класс цитирует предикаты или лямбды, которые они получают в качестве аргументов, когда они упаковывают эту информацию в MethodCallExpression
,
Я попробовал пример (и несколько других за последние пару дней), и нет смысла цитировать лямбду в этом случае.
Вот выражение вызова метода для метода, который ожидает лямбда-выражение (а не экземпляр делегата) в качестве единственного параметра.
Затем я собираю MethodCallExpression
завернув его в лямбду.
Но это не компилирует внутренний LambdaExpression
(аргумент к GimmeExpression
метод), а также. Он оставляет внутреннее лямбда-выражение в виде дерева выражений и не делает его экземпляром делегата.
На самом деле, это работает хорошо без цитирования.
И если я цитирую аргумент, он ломается и выдает ошибку, указывающую, что я передаю неверный тип аргумента GimmeExpression
метод.
В чем дело? Что это за цитата?
private static void TestMethodCallCompilation()
{
var methodInfo = typeof(Program).GetMethod("GimmeExpression",
BindingFlags.NonPublic | BindingFlags.Static);
var lambdaExpression = Expression.Lambda<Func<bool>>(Expression.Constant(true));
var methodCallExpression = Expression.Call(null, methodInfo, lambdaExpression);
var wrapperLambda = Expression.Lambda(methodCallExpression);
wrapperLambda.Compile().DynamicInvoke();
}
private static void GimmeExpression(Expression<Func<bool>> exp)
{
Console.WriteLine(exp.GetType());
Console.WriteLine("Compiling and executing expression...");
Console.WriteLine(exp.Compile().Invoke());
}
1 ответ
Вы должны передать аргумент как ConstantExpression
:
private static void TestMethodCallCompilation()
{
var methodInfo = typeof(Program).GetMethod("GimmeExpression",
BindingFlags.NonPublic | BindingFlags.Static);
var lambdaExpression = Expression.Lambda<Func<bool>>(Expression.Constant(true));
var methodCallExpression =
Expression.Call(null, methodInfo, Expression.Constant(lambdaExpression));
var wrapperLambda = Expression.Lambda(methodCallExpression);
wrapperLambda.Compile().DynamicInvoke();
}
private static void GimmeExpression(Expression<Func<bool>> exp)
{
Console.WriteLine(exp.GetType());
Console.WriteLine("Compiling and executing expression...");
Console.WriteLine(exp.Compile().Invoke());
}
Причина должна быть довольно очевидной - вы передаете постоянное значение, поэтому оно должно быть ConstantExpression
, Передавая выражение напрямую, вы явно говорите "и получите значение exp
из этого сложного дерева выражений ". И так как это дерево выражений на самом деле не возвращает значение Expression<Func<bool>>
, вы получаете ошибку.
Путь IQueryable
Работы не имеют к этому никакого отношения. Методы расширения на IQueryable
должны сохранять всю информацию о выражениях, включая типы и ссылки ParameterExpression
и тому подобное. Это потому, что они на самом деле ничего не делают - они просто строят дерево выражений. Настоящая работа происходит, когда вы звоните queryable.Provider.Execute(expression)
, По сути, именно так полиморфизм сохраняется, хотя мы занимаемся композицией, а не наследованием (реализация интерфейса). Но это значит, что IQueryable
Сами методы расширения не могут делать ярлыки - они ничего не знают о том, как IQueryProvider
на самом деле собирается интерпретировать запрос, поэтому они не могут ничего выбросить.
Однако самое важное преимущество, которое вы получаете от этого, заключается в том, что вы можете составлять запросы и подзапросы. Рассмотрим такой запрос:
from item in dataSource
where item.SomeRelatedItem.Where(subItem => subItem.SomeValue == 42).Count() > 2
select item;
Теперь это переводится примерно так:
dataSource.Where(item => item.SomeRelatedItem.Where(subItem => subItem.SomeValue == 42).Count() > 2);
Внешний запрос довольно очевиден - мы получим Where
с данным предикатом. Внутренний запрос, однако, на самом деле будет Call
в Where
принимая фактический предикат в качестве аргумента.
Убедившись, что фактические вызовы Where
метод на самом деле переводится в Call
из Where
метод, оба эти случая становятся одинаковыми, и ваш LINQProvider на несколько проще:)
Я на самом деле написал поставщиков LINQ, которые не реализуют IQueryable
и которые на самом деле имеют некоторую полезную логику в таких методах, как Where
, Это намного проще и эффективнее, но имеет недостаток, описанный выше - единственный способ обрабатывать подзапросы - это вручную Invoke
Call
выражения, чтобы получить "реальное" выражение предиката. Yikes - это довольно непросто для простого запроса LINQ!
И, конечно же, это помогает вам составлять разных провайдеров с запросами, хотя я на самом деле не видел (м) примеров использования двух совершенно разных провайдеров в одном запросе.
Что касается разницы между Expression.Constant
а также Expression.Quote
Сами они кажутся довольно похожими. Принципиальное отличие состоит в том, что Expression.Constant
будет рассматривать любые замыкания как фактические константы, а не замыкания. Expression.Quote
с другой стороны, сохранит "замыкание" замыканий. Зачем? Потому что сами объекты замыкания также передаются как Expression.Constant
:) И с тех пор IQueryable
деревья делают лямбды-лямбды-лямбды-ноты [...], вы действительно не хотите терять семантику замыкания в любой момент.