Aptallar için ifade ağaçları? [kapalı]


83

Bu senaryodaki aptal benim.

Google'da bunların ne olduğunu okumaya çalıştım ama anlamıyorum. Birisi bana ne oldukları ve neden yararlı oldukları konusunda basit bir açıklama yapabilir mi?

edit: .Net'teki LINQ özelliğinden bahsediyorum.


1
Bu yazının oldukça eski olduğunu biliyorum, ancak son zamanlarda İfade Ağaçlarına bakıyorum. Fluent NHibernate kullanmaya başladıktan sonra ilgilenmeye başladım. James Gregory, statik yansıma olarak bilinen şeyi kapsamlı bir şekilde kullanır ve bir girişe sahiptir: jagregory.com/writings/introduction-to-static-reflection Statik yansıma ve ifade ağaçlarını çalışırken görmek için Fluent NHibernate kaynak kodunu ( fluentnhibernate.org ). Çok temiz ve çok güzel bir konsept.
Jim Schubert

Yanıtlar:


89

İfade ağaçları hakkında okuduğum en iyi açıklama Charlie Calvert'in bu makalesi .

Özetlersek;

Bir ifade ağaç temsil neyi değil, yapmak istediğim nasıl bunu yapmak istiyorum.

Aşağıdaki çok basit lambda ifadesini düşünün:
Func<int, int, int> function = (a, b) => a + b;

Bu ifade üç bölümden oluşmaktadır:

  • Bir beyan: Func<int, int, int> function
  • Bir eşittir operatörü: =
  • Bir lambda ifadesi: (a, b) => a + b;

Değişken function, iki sayının nasıl ekleneceğini bilen ham çalıştırılabilir koda işaret eder .

Temsilciler ve ifadeler arasındaki en önemli fark budur. Geçtiğiniz iki tamsayı ile ne yapacağını bilmeden function(a Func<int, int, int>) 'yı çağırırsınız . İki tane alır ve bir döndürür, kodunuzun bildiği en fazla budur.

Önceki bölümde, ham çalıştırılabilir koda işaret eden bir değişkeni nasıl bildireceğinizi gördünüz. İfade ağaçları çalıştırılabilir kod değildir , bir veri yapısı biçimidir.

Şimdi, delegelerin aksine, kodunuz olabilir bir ifade ağaç yapmak ne demek olduğunu biliyoruz.

LINQ, kodu ifade ağacı adı verilen bir veri yapısına çevirmek için basit bir sözdizimi sağlar. İlk adım, Linq.Expressionsad alanını tanıtmak için bir using ifadesi eklemektir :

using System.Linq.Expressions;

Şimdi bir ifade ağacı oluşturabiliriz:
Expression<Func<int, int, int>> expression = (a, b) => a + b;

Önceki örnekte gösterilen özdeş lambda ifadesi, türünde olduğu bildirilen bir ifade ağacına dönüştürülür Expression<T>. Tanımlayıcı çalıştırılabilir kod expression değildir ; ifade ağacı olarak adlandırılan bir veri yapısıdır.

Bu, bir temsilciyi çağırabileceğiniz gibi bir ifade ağacını çağıramayacağınız, ancak onu analiz edebileceğiniz anlamına gelir. Peki kodunuz değişkeni analiz ederek neyi anlayabilir expression?

// `expression.NodeType` returns NodeType.Lambda.
// `expression.Type` returns Func<int, int, int>.
// `expression.ReturnType` returns Int32.

var body = expression.Body;
// `body.NodeType` returns ExpressionType.Add.
// `body.Type` returns System.Int32.

var parameters = expression.Parameters;
// `parameters.Count` returns 2.

var firstParam = parameters[0];
// `firstParam.Name` returns "a".
// `firstParam.Type` returns System.Int32.

var secondParam = parameters[1].
// `secondParam.Name` returns "b".
// `secondParam.Type` returns System.Int32.

Burada bir ifadeden alabileceğimiz çok fazla bilgi olduğunu görüyoruz.

Ama buna neden ihtiyacımız olsun?

Bir ifade ağacının çalıştırılabilir kodu temsil eden bir veri yapısı olduğunu öğrendiniz. Ancak şimdiye kadar, neden böyle bir dönüşümü yapmak isteyeceğimize dair temel soruyu yanıtlamadık. Bu yazının başında sorduğumuz soru bu ve şimdi cevaplama zamanı.

LINQ to SQL sorgusu, C # programınızın içinde yürütülmez. Bunun yerine, SQL'e çevrilir, bir kablo üzerinden gönderilir ve bir veritabanı sunucusunda yürütülür. Başka bir deyişle, aşağıdaki kod aslında programınızın içinde asla çalıştırılmaz:
var query = from c in db.Customers where c.City == "Nantes" select new { c.City, c.CompanyName };

Önce aşağıdaki SQL deyimine çevrilir ve ardından bir sunucuda çalıştırılır:
SELECT [t0].[City], [t0].[CompanyName] FROM [dbo].[Customers] AS [t0] WHERE [t0].[City] = @p0

Bir sorgu ifadesinde bulunan kod, başka bir işleme dizge olarak gönderilebilecek bir SQL sorgusuna çevrilmelidir. Bu durumda, bu işlem bir SQL sunucu veritabanı olur. İfade ağacı gibi bir veri yapısını SQL'e çevirmek, ham IL'yi veya çalıştırılabilir kodu SQL'e çevirmekten çok daha kolay olacaktır. Problemin zorluğunu biraz abartmak için, bir dizi sıfır ve birleri SQL'e çevirmeye çalıştığınızı hayal edin!

Sorgu ifadenizi SQL'e çevirme zamanı geldiğinde, sorgunuzu temsil eden ifade ağacı, önceki bölümde basit lambda ifade ağacımızı ayırdığımız gibi, parçalara ayrılır ve analiz edilir. Verilen, LINQ to SQL ifade ağacını ayrıştırmak için kullanılan algoritma, kullandığımızdan çok daha karmaşıktır, ancak prensip aynıdır. İfade ağacının bölümlerini analiz ettikten sonra, LINQ bunları gözden geçirir ve istenen verileri döndürecek bir SQL ifadesi yazmanın en iyi yoluna karar verir.

Sorgu ifadesi gibi bir kodu, başka bir işleme geçirilebilen ve orada çalıştırılabilen bir dizgeye dönüştürme görevini yapmak için ifade ağaçları oluşturulmuştur. Bu kadar basit. Burada büyük bir gizem yok, sallaması gereken sihirli bir değnek yok. Biri basitçe kodu alır, veriye dönüştürür ve daha sonra başka bir sürece aktarılabilecek bir dizgeye çevrilecek kurucu parçaları bulmak için verileri analiz eder.

Sorgu, derleyiciye böyle soyut bir veri yapısında kapsüllenmiş olarak geldiğinden, derleyici onu neredeyse istediği şekilde yorumlamakta özgürdür. Sorguyu belirli bir sırada veya belirli bir şekilde yürütmeye zorlanmaz. Bunun yerine, ifade ağacını analiz edebilir, ne yapmak istediğinizi keşfedebilir ve sonra nasıl yapılacağına karar verebilir. En azından teoride, mevcut ağ trafiği, veri tabanındaki yük, mevcut sonuç kümeleri vb. Gibi herhangi bir sayıda faktörü dikkate alma özgürlüğüne sahiptir. Pratikte LINQ to SQL tüm bu faktörleri dikkate almaz. , ancak teoride ne isterse hemen hemen yapmak ücretsizdir. Dahası, bu ifade ağacını, elle yazdığınız, onu analiz edebilecek ve LINQ'dan SQL'e çok farklı bir şeye çevirebilecek özel bir koda geçirebilirsiniz.

Bir kez daha, ifade ağaçlarının yapmak istediğimiz şeyi temsil etmemize (ifade etmemize) izin verdiğini görüyoruz . Ve ifadelerimizin nasıl kullanılacağına karar veren çevirmenler kullanıyoruz.


2
Daha iyi cevaplardan biri.
johnny

4
mükemmel cevap. Bu parlak açıklamaya eklenecek küçük bir özellik şudur - ifade ağaçlarının başka bir kullanımı, ifade ağacını çalıştırma sırasında beslemeden önce uygun görebileceğiniz için çalışma zamanında değiştirebilmenizdir ve bu da bazen son derece yararlıdır.
Yan D

41

İfade ağacı, çalıştırılabilir kodu verilere çevirmek için bir mekanizmadır. Bir ifade ağacı kullanarak, programınızı temsil eden bir veri yapısı oluşturabilirsiniz.

C # 'da, Expression<T>sınıfı kullanarak lambda ifadeleri tarafından üretilen ifade ağacıyla çalışabilirsiniz .


Geleneksel bir programda şu şekilde kod yazarsınız:

double hypotenuse = Math.Sqrt(a*a + b*b);

Bu kod, derleyicinin bir atama oluşturmasına neden olur ve işte bu kadar. Çoğu durumda, tek önemsediğiniz budur.

Geleneksel kodla, uygulamanız geriye dönük olarak geri gidemez ve hypotenusebir Math.Sqrt()arama yaparak üretildiğini belirlemek için bakamaz; bu bilgi içerilenlerin bir parçası değildir.

Şimdi, aşağıdaki gibi bir lambda ifadesini düşünün:

Func<int, int, double> hypotenuse = (a, b) => Math.Sqrt(a*a + b*b);

Bu öncekinden biraz farklı. Şimdi hypotenuseaslında bir çalıştırılabilir kod bloğuna referanstır . Eğer ararsan

hypotenuse(3, 4);

5döndürülen değeri alacaksınız .

Üretilen çalıştırılabilir kod bloğunu keşfetmek için ifade ağaçlarını kullanabiliriz . Bunun yerine şunu deneyin:

Expression<Func<int, int, int>> addTwoNumbersExpression = (x, y) => x + y;
BinaryExpression body = (BinaryExpression) addTwoNumbersExpression.Body;
Console.WriteLine(body);

Bu şunları üretir:

(x + y)

İfade ağaçları ile daha gelişmiş teknikler ve manipülasyonlar mümkündür.


7
Tamam sonuna kadar seninleydim ama hala bunun neden önemli olduğunu gerçekten anlamıyorum. Uygulamaları düşünmekte zorlanıyorum.

1
Basitleştirilmiş bir örnek kullanıyordu; gerçek güç, ifade ağacını araştıran kodunuzun, onu yorumlamaktan ve ifadeye anlamsal anlam uygulamaktan da sorumlu tutulabilmesinde yatmaktadır.
Pierreten

2
Evet, (x + y) neden bizim için gerçekten yararlı olduğunu açıklasaydı bu cevap daha iyi olurdu. Neden (x + y) keşfetmek isteyelim ve bunu nasıl yaparız?
Paul Matthews

Keşfetmek zorunda değilsiniz, sadece sorgunuzun ne olduğunu ve bu durumda SQL'e
neyin

15

İfade ağaçları, bir ifadenin bellek içi temsilidir, örneğin bir aritmetik veya mantıksal ifade. Örneğin, aritmetik ifadeyi düşünün

a + b*2

*, + 'Dan daha yüksek bir operatör önceliğine sahip olduğundan, ifade ağacı şu şekilde oluşturulur:

    [+]
  /    \
 a     [*]
      /   \
     b     2

Bu ağaca sahip olarak, a ve b'nin herhangi bir değeri için değerlendirilebilir. Ek olarak, örneğin ifadeyi türetmek için onu diğer ifade ağaçlarına dönüştürebilirsiniz.

Bir ifade ağacı uyguladığınızda, bir temel sınıf İfade oluşturmanızı öneririm . Bundan türetilen BinaryExpression sınıfı , + ve * gibi tüm ikili ifadeler için kullanılacaktır. Ardından , referans değişkenlerine (a ve b gibi) bir VariableReferenceExpression ve başka bir sınıf ConstantExpression (örnekteki 2 için ) ekleyebilirsiniz .

İfade ağacı çoğu durumda bir girdinin ayrıştırılmasının sonucu olarak oluşturulur (doğrudan kullanıcıdan veya bir dosyadan). İfade ağacını değerlendirmek için Ziyaretçi modelini kullanmanızı öneririm .


15

Kısa cevap: Aynı tür LINQ sorgusu yazabilmek ve onu herhangi bir veri kaynağına yönlendirebilmek güzel. O olmadan "Dil Entegreli" sorgunuz olamaz.

Uzun cevap: Muhtemelen bildiğiniz gibi, kaynak kodu derlerken, onu bir dilden diğerine dönüştürüyorsunuz. Genellikle yüksek seviyeli bir dilden (C #) daha düşük bir kaldıraca (IL).

Bunu yapmanın temelde iki yolu vardır:

  1. Kodu bul ve değiştir kullanarak çevirebilirsiniz
  2. Kodu ayrıştırır ve bir ayrıştırma ağacı alırsınız.

İkincisi, 'derleyiciler' olarak bildiğimiz tüm programların yaptığı şeydir.

Bir ayrıştırma ağacına sahip olduğunuzda, onu başka bir dile kolayca çevirebilirsiniz ve bu, ifade ağaçlarının yapmamıza izin verdiği şeydir. Kod veri olarak saklandığından, istediğiniz her şeyi yapabilirsiniz, ancak muhtemelen onu başka bir dile çevirmek isteyeceksiniz.

Şimdi, LINQ to SQL'de ifade ağaçları bir SQL komutuna dönüştürülür ve ardından kablo üzerinden veritabanı sunucusuna gönderilir. Bildiğim kadarıyla kodu çevirirken gerçekten süslü bir şey yapmıyorlar ama yapabilirler . Örneğin, sorgu sağlayıcısı, ağ koşullarına bağlı olarak farklı SQL kodu oluşturabilir.


6

IIUC, bir ifade ağacı Soyut Sözdizimi Ağacına benzer, ancak bir ifade genellikle tek bir değer verirken, bir AST tüm bir programı temsil edebilir (sınıflar, paketler, işlev, ifadeler vb.)

Her neyse, (2 + 3) * 5 ifadesi için ağaç:

    *
   / \ 
  +   5
 / \
2   3

Kök düğümdeki değeri, yani ifadenin değerini elde etmek için her düğümü yinelemeli olarak (aşağıdan yukarıya) değerlendirin.

Elbette tekli (olumsuzluk) veya üçlü (eğer-ise-değilse) operatörlere ve ifade diliniz izin veriyorsa işlevlere (n-ary, yani herhangi bir sayıda işlem) sahip olabilirsiniz.

Türleri değerlendirmek ve tür kontrolü yapmak, benzer ağaçlar üzerinde yapılır.


5

DLR
İfade ağaçları, Dinamik Dil Çalışma Süresini (DLR) desteklemek için C # 'a bir ektir. DLR, bize değişkenleri bildirmek için "var" yöntemini vermekten de sorumlu olan şeydir. ( var objA = new Tree();)

DLR hakkında daha fazla bilgi .

Esasen Microsoft, CLR'yi LISP, SmallTalk, Javascript, vb. Gibi dinamik diller için açmak istedi. Bunu yapmak için, ifadeleri anında ayrıştırıp değerlendirebilmeleri gerekiyordu. DLR ortaya çıkmadan önce bu mümkün değildi.

İlk cümleye dönersek, İfade ağaçları, DLR kullanma yeteneğini açan C # 'a bir eklemedir. Bundan önce, C # çok daha statik bir dildi - tüm değişken türlerinin belirli bir tür olarak bildirilmesi ve tüm kodun derleme zamanında yazılması gerekiyordu.

Bunu Veri
İfadesi ağaçları ile kullanmak , taşkın kapılarını dinamik koda açar.

Diyelim ki bir emlak sitesi oluşturuyorsunuz. Tasarım aşamasında uygulayabileceğiniz tüm filtreleri bilirsiniz. Bu kodu uygulamak için iki seçeneğiniz vardır: her veri noktasını bir dizi If-Then denetimi ile karşılaştıran bir döngü yazabilirsiniz; veya dinamik bir dilde (SQL) bir sorgu oluşturmayı deneyebilir ve bunu sizin için aramayı gerçekleştirebilecek bir programa (veritabanı) aktarabilirsiniz.

İfade ağaçları ile artık programınızdaki kodu - anında - değiştirebilir ve aramayı gerçekleştirebilirsiniz. Bunu özellikle LINQ aracılığıyla yapabilirsiniz.

(Daha fazla bilgi edinin: MSDN: Nasıl Yapılır: Dinamik Sorgular Oluşturmak için İfade Ağaçlarını Kullanma ).

Verilerin ötesinde
İfade Ağaçlarının birincil kullanımları verileri yönetmek içindir. Ancak, dinamik olarak üretilen kod için de kullanılabilirler. Bu nedenle, dinamik olarak tanımlanan bir işlev istiyorsanız (javascript), bir İfade Ağacı oluşturabilir, onu derleyebilir ve sonuçları değerlendirebilirsiniz.

Biraz daha derinlemesine giderdim, ancak bu site çok daha iyi bir iş çıkarıyor:

Derleyici Olarak İfade Ağaçları

Listelenen örnekler arasında değişken türler için jenerik operatörler oluşturma, elle yuvarlanan lambda ifadeleri, yüksek performanslı sığ klonlama ve bir nesneden diğerine okuma / yazma özelliklerinin dinamik olarak kopyalanması yer alır.

Özet
İfade Ağaçları, çalışma zamanında derlenen ve değerlendirilen kod temsilleridir. Veri işleme ve dinamik programlama için yararlı olan dinamik türlere izin verirler.


Evet, oyuna geç kaldığımı biliyorum ama bu cevabı kendi kendime anlayabilmek için yazmak istedim. (Bu soru internet
Richard

İyi iş. Bu iyi bir cevap.
Rich Bryant

5
"Var" anahtar kelimesinin DLR ile ilgisi yoktur. Onu "dinamik" ile karıştırıyorsun.
Yarik

Bu, Yarık'ın doğru olduğunu gösteren var üzerine güzel, küçük bir cevap. Ancak cevabın geri kalanı için minnettarım. quora.com/…
johnny

1
Bunların hepsi yanlış. varderleme zamanı sözdizimsel bir şekerdir - ifade ağaçları, DLR veya çalışma zamanı ile ilgisi yoktur. var i = 0yazmış gibi derlenir int i = 0, böylece varderleme zamanında bilinmeyen bir türü temsil etmek için kullanamazsınız. İfade ağaçları "DLR'yi desteklemeye ek" değildir, LINQ'ya izin vermek için .NET 3.5'te tanıtılmıştır. Öte yandan DLR, dinamik dillere (IronRuby gibi) ve dynamicanahtar kelimeye izin vermek için .NET 4.0'da tanıtıldı . İfade ağaçları aslında birlikte çalışmayı sağlamak için DLR tarafından kullanılır, bunun tam tersi değildir.
Şafak Gür

-3

Bahsettiğiniz ifade ağacı İfade Değerlendirme ağacı mı?

Evet ise, o zaman ayrıştırıcı tarafından oluşturulan ağaçtır. Parser, Programdan Tokenları tanımlamak için Lexer / Tokenizer'ı kullandı. Parser, İkili ağacı simgelerden oluşturur.

İşte ayrıntılı açıklama


OP'nin atıfta bulunduğu bir İfade Ağacının, bir ayrıştırma ağacı ile benzer ve aynı temel kavramla çalıştığı doğru olsa da, kodla çalışma zamanında dinamik olarak yapılır, ancak Roslyn derleyicisinin girişinde şu satıra dikkat edin: İkisi arasındaki ayrım tamamen kaldırılmasa da gerçekten bulanıklaştı.
yoel halb
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.