Коротка відповідь:
Оператор котирувань - це оператор, який індукує семантику закриття на своєму операнді . Константи - це просто значення.
Цитати та константи мають різне значення, а тому мають різне подання у дереві виразів . Наявність однакового подання для двох дуже різних речей надзвичайно заплутано та схильне до помилок.
Довга відповідь:
Розглянемо наступне:
(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", ...
бла-бла-бла, десятки рядків коду відбиття, щоб зробити лямбду. Призначення оператора quote - повідомити компілятору дерева виразів, що ми хочемо, щоб дана лямбда розглядалася як дерево виразів, а не як функція, без необхідності явно генерувати код генерування дерева виразів .
Найпростіший спосіб:
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));
І справді, якщо ви скомпілюєте та запустите цей код, ви отримаєте правильну відповідь.
Зверніть увагу, що оператор котирувань - це оператор, який індукує семантику закриття внутрішньої лямбди, яка використовує зовнішню змінну, формальний параметр зовнішньої лямбди.
Питання: чому б не усунути Цитату і не змусити це робити те саме?
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 році, все ще є. FYI, піднятий зовнішній параметр знімається у константу, коли цитується Дерево виразів переорієнтоване як делегат компілятором середовища виконання. Була вагома причина, чому я написав код таким чином, якого я не пам'ятаю в цей момент, але він має неприємний побічний ефект від введення закриття над значеннями зовнішніх параметрів замість закриття змінних. Очевидно, команда, яка успадкувала цей код, вирішила не виправляти цю ваду, тому, якщо ви покладаєтесь на мутацію закритого зовнішнього параметра, що спостерігається у складеному цитованому інтер’єрному лямбда, ви будете розчаровані. Однак, оскільки є досить поганою практикою програмування як (1) мутувати формальний параметр, так і (2) покладатися на мутацію зовнішньої змінної, я б рекомендував вам змінити програму, щоб не використовувати ці дві погані практики програмування, а не чекаючи виправлення, яке, здається, не відбудеться. Вибачення за помилку.)
Отже, щоб повторити запитання:
Компілятор C # міг бути створений для компіляції вкладених лямбда-виразів у дерево виразів із залученням Expression.Constant () замість Expression.Quote () та будь-якого постачальника запитів LINQ, який хоче обробити дерева виразів якоюсь іншою мовою запитів (наприклад, SQL ) міг би шукати ConstantExpression з типом Expression замість UnaryExpression зі спеціальним типом вузла Quote, а все інше було б однаковим.
Ви праві. Ми могли б закодувати семантичну інформацію, що означає "наводити семантику закриття на це значення", використовуючи тип константного виразу як прапор .
Тоді "Постійна" мала б значення "використовувати це значення константи, якщо тільки тип не є типом дерева виразів і значення не є дійсним деревом виразів, і в цьому випадку замість цього використовуйте значення, яке є деревом виразів, отриманим в результаті переписування інтер'єр даного дерева виразів, щоб викликати семантику закриття в контексті будь-яких зовнішніх лямбд, які ми можемо знаходити зараз.
Але чому б нам зробити цю божевільну річ? Оператор котирувань - це шалено складний оператор , і його слід використовувати явно, якщо ви збираєтеся ним користуватися. Ви пропонуєте, щоб зважаючи на те, щоб не додавати один зайвий заводський метод і тип вузла серед кількох десятків уже наявних, ми додали химерний кутовий регістр до констант, щоб константи іноді були логічно константами, а іноді їх переписували лямбди з семантикою закриття.
Це також мало б якийсь дивний ефект, що константа не означає "використовувати це значення". Припустимо, з якоїсь химерної причини ви хотіли, щоб третій випадок зкомпілював дерево виразів у делегат, який передає дерево виразів, яке має не переписане посилання на зовнішню змінну? Чому? Можливо, тому, що ви тестуєте свій компілятор і хочете просто передати константу, щоб потім можна було виконати якийсь інший аналіз. Ваша пропозиція зробить це неможливим; будь-яка константа, яка має тип дерева виразів, буде переписана незалежно. Можна обгрунтовано сподіватися, що «константа» означає «використовувати це значення». "Постійний" - це вузол "роби те, що я кажу". Постійний процесор ' сказати на основі типу.
І зауважте, звичайно, що ви тепер покладаєте тягар розуміння (тобто розуміння того, що константа ускладнює семантику, яка в одному випадку означає «константа», і «індукує семантику закриття» на основі прапора, що є в системі типів ) на кожен постачальник, який проводить семантичний аналіз дерева виразів, а не лише постачальників корпорації Майкрософт. Скільки цих сторонніх постачальників помиляються?
"Цитата" розмахує великим червоним прапором, на якому написано: "привіт, приятелю, заглянь сюди, я вкладений лямбда-вираз і маю шалену семантику, якщо я закритий над зовнішньою змінною!" тоді як "Констант" говорить: "Я не що інше, як цінність; використовуйте мене, як вважаєте за потрібне". Коли щось складне та небезпечне, ми хочемо, щоб це робило хвильовими червоними прапорами, не приховуючи цього, змушуючи користувача перекопувати систему типів , щоб з’ясувати, чи є це значення особливим чи ні.
Крім того, думка, що уникнення надмірності є навіть метою, є неправильною. Звичайно, уникнення непотрібного, заплутаного надмірності - це мета, але більшість надмірностей - це добре; надмірність створює ясність. Нові заводські методи та типи вузлів дешеві . Ми можемо зробити стільки, скільки нам потрібно, щоб кожна представляла одну операцію чисто. Нам не потрібно вдаватися до неприємних хитрощів на кшталт "це означає одне, якщо це поле не встановлено для цього, а в цьому випадку це означає щось інше".