Expression.Quote (), Expression.Constant () şimdiden ne yapamaz?


98

Not: " LINQ'nun Expression.Quote yönteminin amacı nedir? " Sorusunun farkındayım. ” , Ancak okumaya devam ederseniz soruma cevap vermediğini göreceksiniz.

Belirtilen amacın ne olduğunu anlıyorum Expression.Quote(). Bununla birlikte, Expression.Constant()aynı amaç için kullanılabilir ( Expression.Constant()halihazırda kullanılan tüm amaçlara ek olarak ). Bu nedenle, neden Expression.Quote()gerekli olduğunu anlamıyorum .

Bunu göstermek için, geleneksel olarak kullanılacağı hızlı bir örnek yazdım Quote(ünlem işaretleriyle işaretlenmiş çizgiye bakın), ancak Constantbunun yerine kullandım ve eşit derecede iyi çalıştı:

string[] array = { "one", "two", "three" };

// This example constructs an expression tree equivalent to the lambda:
// str => str.AsQueryable().Any(ch => ch == 'e')

Expression<Func<char, bool>> innerLambda = ch => ch == 'e';

var str = Expression.Parameter(typeof(string), "str");
var expr =
    Expression.Lambda<Func<string, bool>>(
        Expression.Call(typeof(Queryable), "Any", new Type[] { typeof(char) },
            Expression.Call(typeof(Queryable), "AsQueryable",
                            new Type[] { typeof(char) }, str),
            // !!!
            Expression.Constant(innerLambda)    // <--- !!!
        ),
        str
    );

// Works like a charm (prints one and three)
foreach (var str in array.AsQueryable().Where(expr))
    Console.WriteLine(str);

Çıktısı expr.ToString()her ikisi için de aynıdır (kullansam Constantda kullansam da Quote).

Yukarıdaki gözlemler göz önüne alındığında, bu Expression.Quote()gereksiz görünüyor . C # derleyicisi, yuvalanmış lambda ifadelerini Expression.Constant()yerine içeren bir ifade ağacında derlemek için yapılmış olabilir Expression.Quote()ve ifade ağaçlarını başka bir sorgu diline (SQL gibi) işlemek isteyen herhangi bir LINQ sorgu sağlayıcısı, yerine bir ConstantExpressionwith türü Expression<TDelegate>arayabilir. bir UnaryExpressionözel olan Quotedüğüm türü ve her şey aynı olacaktır.

Neyi kaçırıyorum? Neden icat edildi Expression.Quote()ve özel Quotedüğüm türü UnaryExpression?

Yanıtlar:


191

Kısa cevap:

Alıntı operatör olan operatör kendi işlenen üzerindeki kapatma semantiğini neden . Sabitler sadece değerlerdir.

Tırnakların ve sabitlerin farklı anlamları vardır ve bu nedenle bir ifade ağacında farklı temsilleri vardır . Çok farklı iki şey için aynı temsile sahip olmak son derece kafa karıştırıcı ve hataya açıktır.

Uzun cevap:

Aşağıdakileri göz önünde bulundur:

(int s)=>(int t)=>s+t

Dış lambda, dış lambda parametresine bağlı toplayıcılar için bir fabrikadır.

Şimdi, bunu daha sonra derlenecek ve çalıştırılacak bir ifade ağacı olarak göstermek istediğimizi varsayalım. İfade ağacının gövdesi ne olmalıdır? Derlenmiş durumun bir temsilci mi yoksa bir ifade ağacı mı döndürmesini istediğinize bağlıdır.

İlginç olmayan davayı reddederek başlayalım. Bir temsilcinin geri dönmesini istiyorsak, o zaman Quote veya Constant kullanılıp kullanılmayacağı bir tartışma konusudur:

        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 iç içe geçmiş bir lambdaya sahiptir; derleyici, iç lambda'yı dış lambda için oluşturulan işlevin durumu üzerinde kapalı bir işleve temsilci olarak üretir. Bu davayı daha fazla düşünmemize gerek yok.

Diyelim ki derlenmiş durum , iç kısımda bir ifade ağacı döndürmesini istiyoruz . Bunu yapmanın iki yolu vardır: kolay yol ve zor yol.

Zor yol, bunu söylemek yerine

(int s)=>(int t)=>s+t

gerçekten demek istediğimiz

(int s)=>Expression.Lambda(Expression.Add(...

Sonra için ifade ağacı oluşturmak olduğunu üreten, bu pisliği :

        Expression.Lambda(
            Expression.Call(typeof(Expression).GetMethod("Lambda", ...

blah blah blah, lambda yapmak için düzinelerce yansıma kodu satırı. Alıntı operatörünün amacı, ifade ağacı derleyicisine, verilen lambda'nın, ifade ağacı oluşturma kodunu açıkça oluşturmak zorunda kalmadan bir işlev olarak değil, bir ifade ağacı olarak değerlendirilmesini istediğimizi söylemektir .

Kolay yol:

        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));

Ve gerçekten, eğer bu kodu derler ve çalıştırırsanız doğru cevabı alırsınız.

Alıntı operatörünün, bir dış değişkeni, dış lambda'nın biçimsel bir parametresini kullanan iç lambda üzerinde kapanış semantiğini indükleyen operatör olduğuna dikkat edin.

Soru şudur: Neden Alıntıyı kaldırıp aynı şeyi yaptırmıyorsunuz?

        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));

Sabit, kapanış anlambilimine neden olmaz. Neden yapmalı? Bunun bir sabit olduğunu söyledin . Bu sadece bir değer. Derleyiciye verildiği gibi mükemmel olmalıdır; derleyici, ihtiyaç duyulan yığına sadece bu değerin bir dökümünü oluşturabilmelidir.

Endüklenen bir kapanma olmadığından, bunu yaparsanız, çağrıda 'System.Int32' türünde bir "değişken" s "tanımlı değil" istisnası alırsınız.

(Bir kenara: Alıntılanan ifade ağaçlarından temsilci oluşturma için kod oluşturucuyu henüz gözden geçirdim ve ne yazık ki 2006'da koda eklediğim bir yorum hala var. Bilginize, kaldırılan dış parametrenin anlık görüntüsü , alıntı yapıldığında bir sabite alınır ifade ağacı, çalışma zamanı derleyicisi tarafından bir temsilci olarak yeniden tanımlandı.Kodu tam olarak şu anda hatırlamadığım şekilde yazmamın iyi bir nedeni vardı, ancak dış parametrelerin değerleri üzerinde kapatma getirmenin kötü bir yan etkisi var değişkenleri kapatmak yerine. Görünüşe göre bu kodu miras alan ekip, bu kusuru düzeltmemeye karar verdi, bu nedenle, derlenmiş bir alıntılanmış iç lambdada gözlemlenen kapalı bir dış parametrenin mutasyonuna güveniyorsanız, hayal kırıklığına uğrayacaksınız. Bununla birlikte, hem (1) resmi bir parametreyi mutasyona uğratmak hem de (2) bir dış değişkenin mutasyonuna güvenmek oldukça kötü bir programlama uygulaması olduğundan, programınızı bu iki kötü programlama uygulamasını kullanmayacak şekilde değiştirmenizi tavsiye ederim. gelecek gibi görünmeyen bir düzeltmeyi bekliyorum. Hata için özür dileriz.)

Öyleyse soruyu tekrarlamak için:

C # derleyicisi, yuvalanmış lambda ifadelerini Expression.Quote () yerine Expression.Constant () ve ifade ağaçlarını başka bir sorgu diline (SQL gibi) işlemek isteyen herhangi bir LINQ sorgu sağlayıcısını içeren bir ifade ağacında derlemek için yapılmış olabilirdi. ), özel Quote düğüm tipine sahip bir UnaryExpression yerine Expression türünde bir ConstantExpression arayabilir ve diğer her şey aynı olur.

Haklısın. Biz olabilir aracı tarafından "Bu değerin üzerindeki kapatma semantiğini teşvik" olduğunu anlamsal bilgiyi kodlamak bir bayrak olarak sabit ifadenin türünü kullanarak .

Bu durumda "Sabit", " tür bir ifade ağacı türü olmadıkça ve değer geçerli bir ifade ağacı olmadığı sürece bu sabit değeri kullan" anlamına gelir ; Verilen ifade ağacının içi, şu anda içinde olabileceğimiz herhangi bir dış lambdalar bağlamında kapanış anlambilimini indüklemek için.

Ama neden olur biz o çılgın şey? Alıntı operatörü delice karmaşık bir operatördür ve kullanacaksanız açık bir şekilde kullanılmalıdır . Halihazırda var olan birkaç düzine arasına fazladan bir fabrika yöntemi ve düğüm türü eklememek konusunda cimri olmak için, sabitlere tuhaf bir köşe durumu eklediğimizi, böylece sabitlerin bazen mantıksal sabitler olmasını ve bazen yeniden yazılmalarını öneriyorsunuz. kapanış semantiği ile lambdas.

Ayrıca sabitin "bu değeri kullan" anlamına gelmemesi biraz garip bir etkiye sahip olacaktır. Tuhaf bir nedenden ötürü , yukarıdaki üçüncü vakanın, bir dış değişkene yeniden yazılmamış bir referansı olan bir ifade ağacını dağıtan bir temsilciye bir ifade ağacını derlemesini istediğinizi varsayalım. Neden? Belki de derleyicinizi test ettiğiniz ve sabiti geçmek istediğiniz için, daha sonra başka analizler yapabilmeniz için. Öneriniz bunu imkansız kılacaktır; ifade ağacı türünde olan herhangi bir sabit ne olursa olsun yeniden yazılacaktır. "Sabit" in "bu değeri kullan" anlamına geldiğine dair makul bir beklenti var. "Sabit" bir "dediğimi yap" düğümüdür. Sabit işlemci ' türüne göre söylemek.

Ve elbette şimdi anlama yükünü (yani, sabitin bir durumda "sabit" anlamına gelen karmaşık anlambilimlere sahip olduğunu ve tür sistemindeki bir bayrağa dayalı "kapanış anlambilimini indüklemek" ) her şeyin üzerine yüklediğinizi unutmayın. yalnızca Microsoft sağlayıcıları üzerinde değil, bir ifade ağacının anlamsal analizini yapan sağlayıcı. Bu üçüncü taraf sağlayıcılardan kaç tanesi yanlış anlar?

"Alıntı", "hey dostum, buraya bak, iç içe geçmiş bir lambda ifadesiyim ve bir dış değişkeni kapatırsam tuhaf anlamlara sahibim!" Yazan büyük bir kırmızı bayrak sallıyor. "Sabit" ise "Ben bir değerden başka bir şey değilim; beni uygun gördüğünüz gibi kullanın." Bir şey karmaşık ve tehlikeli olduğunda, bu değerin özel olup olmadığını anlamak için kullanıcıyı tip sistemini kazarak bu gerçeği gizlemek yerine, onu kırmızı bayraklar dalgalandırmak istiyoruz .

Dahası, fazlalıktan kaçınmanın bir amaç olduğu fikri bile yanlıştır. Elbette, gereksiz, kafa karıştırıcı fazlalıklardan kaçınmak bir hedeftir, ancak çoğu fazlalık iyi bir şeydir; fazlalık netlik yaratır. Yeni fabrika yöntemleri ve düğüm türleri ucuzdur . İhtiyaç duyduğumuz kadarını yapabiliriz, böylece her biri tek bir işlemi temiz bir şekilde temsil eder. "Bu alan bu şeye ayarlanmadıkça bu bir anlama gelir, bu durumda başka bir anlama gelir" gibi çirkin numaralara başvurmamıza gerek yok.


11
Şimdi utandım çünkü kapanış anlambilimini düşünmedim ve iç içe yerleştirilmiş lambda'nın bir dış lambdadan bir parametre yakaladığı bir durumu test edemedim. Bunu yapsaydım, farkı fark ederdim. Cevabınız için tekrar çok teşekkürler.
Timwi

19

Bu soru zaten mükemmel bir cevap aldı. Ek olarak, ifade ağaçları ile ilgili sorularda faydalı olabilecek bir kaynağa işaret etmek istiyorum:

Orada dır-dir Microsoft tarafından adı verilen bir CodePlex projesiydi Dinamik Dil Çalışma Zamanı. Belgeleri, başlıklı belgeyi içerir,"İfade Ağaçları v2 Spesifikasyonu", tam olarak bu: .NET 4'teki LINQ ifade ağaçlarının belirtimi.

Güncelleme: CodePlex geçersiz. Spec (PDF) v2 İfade Ağaçları GitHub'dan taşındı .

Örneğin, şu konu hakkında şunları söylüyor Expression.Quote:

4.4.42 Alıntı

Expression türünde "sabit" bir değere sahip bir ifadeyi temsil etmek için UnaryExpressions'da Quote kullanın. Sabit düğümden farklı olarak, Quote düğümü özel olarak ParameterExpression düğümlerini içerir. Kapsanan bir ParameterExpression düğümü, sonuçta ortaya çıkan ifadede kapatılacak bir yerel bildirirse, Quote, referans konumlarında ParameterExpression'un yerini alır. Quote düğümü değerlendirildiğinde çalışma zamanında, ParameterExpression referans düğümleri için kapanış değişkeni referanslarını değiştirir ve ardından alıntılanan ifadeyi döndürür. […] (S. 63–64)


1
İnsana balık öğret türünün mükemmel yanıtı. Yalnızca belgelerin taşındığını ve artık docs.microsoft.com/en-us/dotnet/framework/… adresinde mevcut olduğunu eklemek isterim . Alıntılanan belge, özellikle GitHub'dadır: github.com/IronLanguages/dlr/tree/master/Docs
relative_random

3

Bundan sonra gerçekten mükemmel bir cevap, anlambilimin ne olduğu açık. Neden bu şekilde tasarlandıkları o kadar net değil , düşünün:

Expression.Lambda(Expression.Add(ps, pt));

Bu lambda derlendiğinde ve çağrıldığında, iç ifadeyi değerlendirir ve sonucu döndürür. Buradaki iç ifade bir eklemedir, dolayısıyla ps + pt değerlendirilir ve sonuç döndürülür. Bu mantığı takiben, aşağıdaki ifade:

Expression.Lambda(
    Expression.Lambda(
              Expression.Add(ps, pt),
            pt), ps);

Dış lambda çağrıldığında bir iç lambda derlenmiş yöntem başvurusu döndürmelidir (çünkü lambda'nın bir yöntem başvurusuna göre derlendiğini söylüyoruz). Öyleyse neden bir Alıntıya ihtiyacımız var ?! Yöntem referansının döndürüldüğü durumu, bu referans çağrısının sonucuyla karşılaştırmak için.

Özellikle:

let f = Func<...>
return f; vs. return f(...);

Bazı nedenlerden dolayı .Net tasarımcıları ilk durum için Expression.Quote (f) ve ikinci durum için düz f'yi seçtiler . Benim görüşüme göre bu, çoğu programlama dilinde bir değer döndürmek doğrudan olduğundan ( Alıntı veya başka bir işleme gerek yoktur ), ancak çağrı fazladan yazma (parantezler + bağımsız değişkenler) gerektirdiğinden , bu büyük bir kafa karışıklığına neden olur . MSIL düzeyinde çağırın . .Net tasarımcıları bunu ifade ağaçları için tam tersi yaptı. Sebebini bilmek ilginç olurdu.


0

Sanırım daha çok verilmiş gibi:

Expression<Func<Func<int>>> f = () => () => 2;

Kişisel ağaçtır Expression.Lambda(Expression.Lambda)ve fbir döndüren bir lambda için Expression Ağacı temsil Func<int>getiriyi 2.

Ancak, istediğiniz şey geri dönen bir lambda için İfade Ağacı'nı döndüren bir lambda ise 2, o zaman ihtiyacınız olan:

Expression<Func<Expression<Func<int>>>> f = () => () => 2;

Ve şimdi ağaçtır Expression.Lambda(Expression.Quote(Expression.Lambda))ve fbir döndüren bir lambda için İfade Ağacı temsil Expression<Func<int>>bir için Expression Ağacı olduğunu Func<int>o getiriler 2.


-2

Sanırım buradaki mesele ağacın dışavurumudur. Temsilciyi içeren sabit ifade, gerçekten sadece bir temsilci olan bir nesneyi içerir. Bu, tekli ve ikili bir ifadeye doğrudan bir kırılmadan daha az ifade edicidir.


Bu mu? Tam olarak nasıl bir ifade katıyor? ConstantExpression ile zaten ifade edemediğiniz UnaryExpression (ki bu da kullanmak için garip bir ifade türüdür) ile ne “ifade edebilirsiniz”?
Timwi
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.