Короткий ответ:
Оператор кавычек - это оператор, который индуцирует семантику закрытия для своего операнда. . Константы - это просто значения.
Кавычки и константы имеют разное значение и поэтому по- разному представлены в дереве выражений . Одно и то же представление двух очень разных вещей чрезвычайно сбивает с толку и подвержено ошибкам.
Длинный ответ:
Учтите следующее:
(int s)=>(int t)=>s+t
Внешняя лямбда - это фабрика для сумматоров, привязанных к параметру внешней лямбды.
Теперь предположим, что мы хотим представить это как дерево выражений, которое позже будет скомпилировано и выполнено. Каким должно быть тело дерева выражений? Это зависит от того, хотите ли вы, чтобы скомпилированное состояние возвращало делегат или дерево выражения.
Начнем с неинтересного случая. Если мы хотим, чтобы он возвращал делегата, вопрос о том, использовать ли Quote или Constant, является спорным:
var ps = Expression.Parameter(typeof(int), "s");
var pt = Expression.Parameter(typeof(int), "t");
var ex1 = Expression.Lambda(
Expression.Lambda(
Expression.Add(ps, pt),
pt),
ps);
var f1a = (Func<int, Func<int, int>>) ex1.Compile();
var f1b = f1a(100);
Console.WriteLine(f1b(123));
Лямбда имеет вложенную лямбду; компилятор генерирует внутреннюю лямбду как делегат функции, закрытой по состоянию функции, созданной для внешней лямбды. Нам больше не нужно рассматривать этот случай.
Предположим, мы хотим, чтобы скомпилированное состояние возвращало дерево выражений интерьера. Есть два способа сделать это: легкий и сложный.
Сложно сказать, что вместо
(int s)=>(int t)=>s+t
что мы на самом деле имеем в виду
(int s)=>Expression.Lambda(Expression.Add(...
А затем сгенерируйте для этого дерево выражений , создав такой беспорядок :
Expression.Lambda(
Expression.Call(typeof(Expression).GetMethod("Lambda", ...
бла-бла-бла, десятки строк кода отражения, чтобы сделать лямбду. Цель оператора кавычки - сообщить компилятору дерева выражений, что мы хотим, чтобы данная лямбда обрабатывалась как дерево выражения, а не как функция, без необходимости явно генерировать код генерации дерева выражений. .
Самый простой способ:
var ex2 = Expression.Lambda(
Expression.Quote(
Expression.Lambda(
Expression.Add(ps, pt),
pt)),
ps);
var f2a = (Func<int, Expression<Func<int, int>>>)ex2.Compile();
var f2b = f2a(200).Compile();
Console.WriteLine(f2b(123));
И действительно, если вы скомпилируете и запустите этот код, вы получите правильный ответ.
Обратите внимание, что оператор кавычек - это оператор, который индуцирует семантику закрытия для внутренней лямбды, которая использует внешнюю переменную, формальный параметр внешней лямбды.
Возникает вопрос: почему бы не удалить Quote и сделать то же самое?
var ex3 = Expression.Lambda(
Expression.Constant(
Expression.Lambda(
Expression.Add(ps, pt),
pt)),
ps);
var f3a = (Func<int, Expression<Func<int, int>>>)ex3.Compile();
var f3b = f3a(300).Compile();
Console.WriteLine(f3b(123));
Константа не вызывает семантику закрытия. Зачем это нужно? Вы сказали, что это постоянно . Это просто ценность. Он должен быть идеальным, как передано компилятору; компилятор должен иметь возможность просто генерировать дамп этого значения в стек, где это необходимо.
Так как закрытие не вызвано, если вы сделаете это, вы получите исключение «переменная типа« System.Int32 »не определена» при вызове.
(Кроме того: я только что рассмотрел генератор кода для создания делегатов из цитируемых деревьев выражений, и, к сожалению, комментарий, который я вставил в код еще в 2006 году, все еще там. К вашему сведению, приподнятый внешний параметр снимается в константу, когда цитируемый дерево выражений реифицируется как делегат компилятором времени выполнения.Я написал код таким образом, по уважительной причине, которую я не помню в данный момент, но у нее есть неприятный побочный эффект, заключающийся в введении замыкания над значениями внешних параметров а не замыкание по переменным. Очевидно, команда, унаследовавшая этот код, решила не исправлять этот недостаток, поэтому, если вы полагаетесь на мутацию закрытого внешнего параметра, наблюдаемую в скомпилированной цитируемой внутренней лямбде, вы будете разочарованы. Однако, поскольку это довольно плохая практика программирования - и (1) изменять формальный параметр, и (2) полагаться на мутацию внешней переменной, я бы порекомендовал вам изменить свою программу, чтобы не использовать эти две плохие практики программирования, а не ожидает исправления, которое, похоже, не появится. Приносим извинения за ошибку.)
Итак, повторяя вопрос:
Компилятор C # можно было заставить компилировать вложенные лямбда-выражения в дерево выражений с использованием Expression.Constant () вместо Expression.Quote () и любого поставщика запросов LINQ, который хочет обрабатывать деревья выражений на каком-либо другом языке запросов (например, SQL ) может искать ConstantExpression с типом Expression вместо UnaryExpression со специальным типом узла Quote, а все остальное будет таким же.
Ты прав. Мы могли бы закодировать семантическую информацию, которая означает «вызвать семантику закрытия для этого значения», используя тип константного выражения в качестве флага .
Тогда "константа" будет иметь значение "использовать это постоянное значение, если только тип не является типом дерева выражения, а значение является допустимым деревом выражения, и в этом случае вместо этого используйте значение, которое является деревом выражения, полученным в результате перезаписи внутренняя часть данного дерева выражений, чтобы вызвать семантику закрытия в контексте любых внешних лямбда-выражений, в которых мы могли бы находиться прямо сейчас.
Но почему бы нам сделать эту сумасшедшую вещь? Оператор кавычек - безумно сложный оператор , и его следует использовать явно, если вы собираетесь его использовать. Вы предлагаете, чтобы не добавлять один дополнительный фабричный метод и тип узла среди нескольких десятков уже имеющихся, чтобы мы добавили к константам причудливый угловой регистр, так что константы иногда являются логическими константами, а иногда их переписывают. лямбды с семантикой замыкания.
Это также будет иметь несколько странный эффект, поскольку константа не означает «использовать это значение». Предположим, по какой-то причудливой причине вы хотите, чтобы в третьем случае выше было скомпилировано дерево выражений в делегат, который передает дерево выражений с непереписанной ссылкой на внешнюю переменную? Зачем? Возможно, потому, что вы тестируете свой компилятор и хотите просто передать константу, чтобы вы могли позже выполнить какой-то другой анализ. Ваше предложение сделало бы это невозможным; любая константа, которая относится к типу дерева выражений, будет перезаписана независимо. Есть разумные основания полагать, что «константа» означает «использовать это значение». «Константа» - это узел «делай то, что я говорю». Постоянный процессор '
И, конечно, обратите внимание, что теперь вы возлагаете бремя понимания (то есть понимание того, что константа имеет сложную семантику, которая означает «константа» в одном случае и «вызывает семантику закрытия» на основе флага, который есть в системе типов ) на каждый поставщик, который выполняет семантический анализ дерева выражений, а не только поставщиков Microsoft. Сколько из этих сторонних поставщиков ошиблись бы?
«Цитата» размахивает большим красным флагом, который говорит: «Эй, приятель, посмотри сюда, я - вложенное лямбда-выражение, и у меня дурацкая семантика, если я закрываюсь по внешней переменной!» тогда как «Константа» говорит: «Я не более чем ценность; используйте меня, как считаете нужным». Когда что-то является сложным и опасным, мы хотим заставить его размахивать красными флажками, а не скрывать этот факт, заставляя пользователя копаться в системе типов , чтобы выяснить, является ли это значение особенным или нет.
Более того, идея о том, что избегание избыточности является даже целью, неверна. Конечно, целью является предотвращение ненужной, сбивающей с толку избыточности, но большая часть избыточности - это хорошо; избыточность создает ясность. Новые фабричные методы и типы узлов дешевы . Мы можем сделать столько, сколько нам нужно, чтобы каждая из них четко представляла одну операцию. Нам не нужно прибегать к неприятным уловкам вроде «это означает одно, если в этом поле не установлено это значение, и в этом случае это означает что-то другое».
На этот вопрос уже есть отличный ответ. Я также хотел бы указать на ресурс, который может оказаться полезным с вопросами о деревьях выражений:
Там
являетсябыл проект CodePlex от Microsoft под названиемСреда выполнения динамического языка. Его документация включает документ под названием,"Деревья выражений, версия 2", что именно так: Спецификация деревьев выражений LINQ в .NET 4.Например, в нем говорится следующее
Expression.Quote
:источник
После этого действительно отличный ответ, понятно, какова семантика. Не очень понятно, почему они так устроены, рассмотрим:
Когда эта лямбда компилируется и вызывается, она оценивает внутреннее выражение и возвращает результат. Внутреннее выражение здесь - дополнение, поэтому вычисляется ps + pt и возвращается результат. Следуя этой логике, следующее выражение:
должен возвращать ссылку на скомпилированный метод внутренней лямбда-функции при вызове внешней лямбда-функции (потому что мы говорим, что лямбда-функция компилируется в ссылку на метод). Так зачем нам цитата ?! Чтобы отличить случай, когда возвращается ссылка на метод, от результата вызова этой ссылки.
В частности:
По какой-то причине дизайнеры .Net выбрали Expression.Quote (f) для первого случая и plain f для второго. На мой взгляд, это вызывает большую путаницу, поскольку в большинстве языков программирования значение возвращается напрямую (нет необходимости в Quote или какой-либо другой операции), но для вызова требуется дополнительная запись (круглые скобки + аргументы), что переводится в какой-то вид вызывать на уровне MSIL. Дизайнеры .Net сделали противоположное для деревьев выражений. Было бы интересно узнать причину.
источник
Я считаю, что это больше похоже на данное:
Ваше дерево
Expression.Lambda(Expression.Lambda)
иf
представляет собой выражение дерева для лямбда , который возвращаетFunc<int>
что возвращается2
.Но если вам нужна лямбда, которая возвращает дерево выражений для возвращаемой лямбды
2
, вам нужно:И теперь ваше дерево
Expression.Lambda(Expression.Quote(Expression.Lambda))
иf
представляет собой выражение дерева для лямбда , который возвращаетExpression<Func<int>>
это дерево выражения для того,Func<int>
что возвращается2
.источник
Думаю, дело здесь в выразительности дерева. Постоянное выражение, содержащее делегат, на самом деле просто содержит объект, который оказывается делегатом. Это менее выразительно, чем прямая разбивка на унарные и бинарные выражения.
источник