Krótka odpowiedź:
Operator cudzysłowu jest operatorem, który wywołuje semantykę zamknięcia w swoim operandzie . Stałe to tylko wartości.
Cytaty i stałe mają różne znaczenia i dlatego mają różne reprezentacje w drzewie wyrażeń . Posiadanie tej samej reprezentacji dwóch bardzo różnych rzeczy jest niezwykle zagmatwane i podatne na błędy.
Długa odpowiedź:
Rozważ następujące:
(int s)=>(int t)=>s+t
Zewnętrzna lambda to fabryka sumatorów, które są powiązane z zewnętrznym parametrem lambda.
Teraz załóżmy, że chcemy przedstawić to jako drzewo wyrażeń, które zostanie później skompilowane i wykonane. Jaka powinna być treść drzewa wyrażeń? Zależy to od tego, czy chcesz, aby stan skompilowany zwracał delegata, czy drzewo wyrażeń.
Zacznijmy od odrzucenia nieciekawej sprawy. Jeśli chcielibyśmy, aby zwrócił delegata, to kwestia, czy użyć cudzysłowu, czy stałej, jest kwestią sporną:
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));
Lambda ma zagnieżdżoną lambdę; kompilator generuje wewnętrzną lambdę jako delegata funkcji zamkniętej na stan funkcji wygenerowanej dla zewnętrznej lambda. Nie musimy więcej rozważać tej sprawy.
Załóżmy, że chcielibyśmy, aby stan kompilacji zwracał drzewo wyrażeń wnętrza. Są na to dwa sposoby: łatwy i trudny.
Trudno jest powiedzieć, że zamiast
(int s)=>(int t)=>s+t
tak naprawdę mamy na myśli
(int s)=>Expression.Lambda(Expression.Add(...
A następnie wygenerować drzewo ekspresyjną że , produkujących ten bałagan :
Expression.Lambda(
Expression.Call(typeof(Expression).GetMethod("Lambda", ...
bla bla bla, dziesiątki wierszy kodu odbicia tworzącego lambdę. Celem operatora cytatu jest poinformowanie kompilatora drzewa wyrażeń, że chcemy, aby dana lambda była traktowana jako drzewo wyrażeń, a nie jako funkcja, bez konieczności jawnego generowania kodu generującego drzewo wyrażeń .
Prosty sposób to:
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));
I rzeczywiście, jeśli skompilujesz i uruchomisz ten kod, otrzymasz właściwą odpowiedź.
Zauważ, że operator cudzysłowu jest operatorem, który wywołuje semantykę zamykania wewnętrznej lambdy, która używa zmiennej zewnętrznej, formalnego parametru zewnętrznej lambdy.
Pytanie brzmi: dlaczego nie wyeliminować Quote i sprawić, by robił to samo?
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));
Stała nie wywołuje semantyki domknięcia. Dlaczego miałoby to robić? Powiedziałeś, że to była stała . To tylko wartość. Powinien być doskonały, jak przekazano kompilatorowi; kompilator powinien być w stanie po prostu wygenerować zrzut tej wartości na stos, tam gdzie jest to potrzebne.
Ponieważ nie ma wywołania zamknięcia, jeśli to zrobisz, otrzymasz wyjątek „zmienna 's' typu„ System.Int32 ”nie jest zdefiniowana”.
(Na marginesie: właśnie przejrzałem generator kodu do tworzenia delegatów z cytowanych drzew wyrażeń i niestety komentarz, który umieściłem w kodzie z powrotem w 2006 roku, wciąż tam jest. FYI, podniesiony parametr zewnętrzny jest migawkowany do stałej, gdy cytowany drzewo wyrażeń jest reifikowane jako delegat przez kompilator środowiska uruchomieniowego. Był dobry powód, dla którego napisałem kod w ten sposób, którego nie pamiętam w tym momencie, ale ma to nieprzyjemny efekt uboczny wprowadzenia domknięcia wartości parametrów zewnętrznych zamiast zamknięcia zmiennych. Najwyraźniej zespół, który odziedziczył ten kod, postanowił nie naprawiać tej usterki, więc jeśli polegasz na mutacji zamkniętego parametru zewnętrznego obserwowanego w skompilowanej, cytowanej wewnętrznej lambdzie, będziesz rozczarowany. Jednakże, ponieważ dość złą praktyką programistyczną jest zarówno (1) mutacja parametru formalnego, jak i (2) poleganie na mutacji zmiennej zewnętrznej, zalecałbym zmianę programu tak, aby nie używał tych dwóch złych praktyk programowania, zamiast czekając na poprawkę, która wydaje się nie nadejść. Przepraszamy za błąd.)
Tak więc, aby powtórzyć pytanie:
Kompilator C # mógł skompilować zagnieżdżone wyrażenia lambda do drzewa wyrażeń obejmującego Expression.Constant () zamiast Expression.Quote () i dowolnego dostawcy zapytań LINQ, który chce przetwarzać drzewa wyrażeń w innym języku zapytań (takim jak SQL ) może szukać ConstantExpression z typem Expression zamiast UnaryExpression ze specjalnym typem węzła Quote, a wszystko inne byłoby takie samo.
Masz rację. My mogli kodować informacje semantyczne, które oznacza „wywoływania semantykę zamknięcia na tej wartości” za pomocą typ stałej ekspresji jako flaga .
„Stała” miałby wtedy znaczenie „użyj tej stałej wartości, chyba że typ jest typem drzewa wyrażenia, a wartość jest prawidłowym drzewem wyrażenia, w takim przypadku zamiast tego użyj wartości będącej drzewem wyrażenia wynikającym z przepisania wnętrza danego drzewa wyrażeń, aby wywołać semantykę domknięcia w kontekście wszelkich zewnętrznych lambd, w których możemy się teraz znajdować.
Ale dlaczego mielibyśmy robić to szalone rzeczy? Operator cudzysłowu jest niesamowicie skomplikowanym operatorem i powinien być używany jawnie, jeśli zamierzasz go używać. Sugerujesz, aby nie dodawać jednej dodatkowej metody fabrycznej i typu węzła spośród kilkudziesięciu już istniejących, aby dodać dziwaczny przypadek narożny do stałych, tak aby stałe były czasami logicznymi stałymi, a czasami są przepisywane lambdy z semantyką zamknięcia.
Miałoby to również nieco dziwny efekt, że stała nie oznacza „użyj tej wartości”. Załóżmy, że z jakiegoś dziwnego powodu chciałbyś, aby trzeci powyższy przypadek skompilował drzewo wyrażeń w delegata, który przekazuje drzewo wyrażenia, które ma nieprzepisane odniesienie do zmiennej zewnętrznej? Czemu? Być może dlatego, że testujesz kompilator i chcesz po prostu przekazać stałą, abyś mógł później przeprowadzić inną analizę. Twoja propozycja uniemożliwiłaby to; każda stała, która jest typu drzewa wyrażenia, zostanie przepisana niezależnie od tego. Można się spodziewać, że „stała” oznacza „użyj tej wartości”. „Stała” to węzeł „rób to, co mówię”. Stały procesor ” powiedzieć na podstawie typu.
Zauważ, oczywiście, że teraz kładziesz ciężar zrozumienia (to znaczy rozumienia, że stała ma skomplikowaną semantykę, która oznacza „stałą” w jednym przypadku i „indukuje semantykę zamknięcia” w oparciu o flagę znajdującą się w systemie typów ) na każdym dostawca, który przeprowadza analizę semantyczną drzewa wyrażeń, a nie tylko dostawców firmy Microsoft. Ilu z tych zewnętrznych dostawców zrobiłoby to źle?
„Cytuj” macha wielką czerwoną flagą, która mówi: „Hej kolego, spójrz tutaj, jestem zagnieżdżonym wyrażeniem lambda i mam zwariowaną semantykę, jeśli jestem zamknięty na zmiennej zewnętrznej!” podczas gdy „Constant” mówi: „Jestem niczym więcej niż wartością; używaj mnie tak, jak uważasz za stosowne”. Kiedy coś jest skomplikowane i niebezpieczne, chcemy sprawić, by machało czerwonymi flagami, nie ukrywając tego faktu, zmuszając użytkownika do przekopywania się przez system typów , aby dowiedzieć się, czy ta wartość jest specjalna, czy nie.
Co więcej, pomysł, że uniknięcie nadmiarowości jest nawet celem, jest błędny. Oczywiście, unikanie niepotrzebnych, mylących redundancji jest celem, ale większość nadmiarowości to dobra rzecz; nadmiarowość zapewnia przejrzystość. Nowe metody fabryczne i rodzaje węzłów są tanie . Możemy zrobić tyle, ile potrzebujemy, aby każda z nich w przejrzysty sposób reprezentowała jedną operację. Nie musimy uciekać się do nieprzyjemnych sztuczek, takich jak „to oznacza jedną rzecz, chyba że to pole jest ustawione na tę rzecz, w którym to przypadku oznacza coś innego”.