Yazılım.
CevapSitesi.com Beta!
Çözüm Noktası
Facebook, Twitter, Google+ veya e-posta ile paylaşın.
| Sorular | Makaleler | Üyeler | Etiketler  | İletişim
Soru sormak ya da cevap vermek için;
giriş yapın veya üye olun.

Sosyal medya hesaplarınızla da giriş yapabilirsiniz.
0

C# İfade Ağaçları (Expression Trees) ve Expression Sınıfı

C# 3.0 ile beraber gelen İfade Ağaçları, kodu ağaç stili (dallanma yapabilen) bir veri yapısında temsil eder. Lambda İfadeleri (Lambda Expressions) de birer İfade Ağacı ile temsil edilir ve bu şekilde Lambda İfadelerini oluşturmak, değiştirmek veya çözümlemek mümkün olur.

Veri yapılarında bir ağaç, her biri 0, 1 ya da 2 adet dal içeren düğümlerden oluşur. Her bir düğümün her bir dalında yine aynı sayılarda elemanlar içerebilen başka düğümler bulunur.

İfadeleri ağaç türü veri yapısında temsil edip işlemenin faydası Lambda İfadelerinin bölümlerine (gövde ve parametreler) çalışma zamanında ayrı ayrı ulaşıp ayırt edebilmemiz ve hatta aracı bir sınıf vasıtasıyla değiştirebilmenizdir. İfadeleri çözümleyen sınıfların bulunduğu System.Linq.Expressions isim alanındaki bir grup sınıf ve özellikle Expression statik sınıfı yardımıyla dinamik olarak da bu ifadeleri oluşturabiliriz (Bu konuya en son değinilecektir).

İfade Ağaçları Komut Lambdalarında (Statement Lambdas) dolayısıyla çok satırlı ifadelerde de çalışmaz. Ancak Expression sınıfının metotları kullanılarak çok satırlı ifadelerin karşılıkları oluşturulabilir.

İfade Ağaçlarının bir düğümünü aşağıdaki şekilde gösterebiliriz.
        Gövde
|----------------------------------|
| --------- |
| | İşlem | |
| ----|---- |
| | |
| |-------|--------| |
| | | |
| Parametre1 Parametre2 |
| |
|----------------------------------|
Veya, .NET Framework sınıf kütüphanesindeki isimleriyle...
        Body (Gövde)
|----------------------------------|
| ------------- |
| | NodeType | |
| |(DüğümTipi)| |
| ------|------ |
| | |
| |-------|--------| |
| | | |
| Left Right |
| (Sol) (Sağ) |
|----------------------------------|


(a, b) => a > b
ifadesi için ağaç gösterimi;
        Gövde
|----------------------------------|
| --------- |
| | > | |
| ----|---- |
| | |
| |-------|--------| |
| | | |
| a b |
| |
|----------------------------------|
şeklinde olacaktır.
(a, b, c) => a + (b / c)
ifadesinin ağaç gösterimi ise;
        Gövde 1
|---------------------------------------------|
| |
| Düğüm Tipi |
| --------- |
| | + | |
| ----|---- |
| | |
| Sol | Sağ |
| |-------|--------| |
| | | |
| a | |
| | |
| | |
| Gövde 2 | |
| |---------------------------------| |
| | | |
| | Düğüm Tipi | |
| | --------- | |
| | | / | | |
| | ----|---- | |
| | | | |
| | Sol | Sağ | |
| | |-------|--------| | |
| | | | | |
| | b c | |
| | | |
| |---------------------------------| |
| |
|---------------------------------------------|

şeklindedir.

Şekillerde görülen yapılarda ifadenin tamamı gövde (body) olarak kabul edilip işlemin (NodeType / Düğüm Tipi) operatörden önceki bölümüne (örneğin +) sol (Left), operatörden sonraki bölüme ise sağ (Right) adı verilmektedir.
Expression Sınıfı

Lambda İfadelerini bir ağaç veri yapısı şeklinde izlemek ve işleyebilmek için bu ifadelerin System.Linq.Expression isim alanında bulunan Expression sınıfı ile ağaç yapısı şekline çevirmemiz gerekir. Lambda İfadeleri delege ile temsil edilebileceklerinden dolayı System.Linq.Expressions isim alanında Lambda İfadelerini temsil etmek için kullanabileceğimiz aşağıdaki hazır delege tanımları mevcuttur.

public delegate TResult Func<TResult>();
public delegate TResult Func<T1, TResult>(T1 arg1);
public delegate TResult Func<T1, T2, TResult>(T1 arg1, T2 arg2);
public delegate TResult Func<T1, T2, T3, TResult>(T1 arg1, T2 arg2, T3 arg3);
public delegate TResult Func<T1, T2, T3, T4, TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4);
Bu konuya yabancı iseniz delege konusuna biraz göz atmanız gerekebilir.

Yine de biraz açıklama yapalım. Üsttekiler birer fonksiyon biçimi tanımlar. Kendileri delege tanımıdır. Daha önce bahselidiği gibi açıkladıkları tipteki fonksiyonlara referans (fonksiyonlara ulaşmak için başvurulacak kaynak / temsilci) olarak kullanılırlar. Fonksiyon biçimlerinde kullanılan T harfinin anlamı Type yani tipdir. T harfinin bulunduğu yerlere herhangi bir tip gelebilir. Result kelimesi ise sonuç veya dönüş değeri anlamında kullanılabilir. Buna göre TResult ifadeleri fonksiyonun döndürdüğü değerleri (yine herhangi bir tip) ifade eder. "arg" (argument / bağımsız değişken veya parametre) ifadesi değişken isim verme şartlarına uyan parametre adlarını, T1, T2, T3, T4 ifadeleri ise parametrelerin tiplerini ifade eder (Tip1, Tip2...).

Örneğin 2. delegede bu delegenin temsil edeceği fonksiyonun herhangi bir tipte (T1 ifadesi) 1 adet parametre alacağı ve herhangi bir tipte bir sonuç döndüreceği (TResult ifadesi) ifade edilmektedir. 1. delege tanımında hiç parametre olmadığı, 3. delege tanımında 2 parametre, 4. delege tanımında 3 parametre ve 5. delege tanımında fonksiyonun 4 parametre alacağı kabul edilmiştir.

Bu delegeler Lambda İfadeleri ile beraber aşağıdaki gibi kullanılır...

Func<int, int, bool> dfonk = (a, b) => a == b;
Yani
(a, b) => a == b
Lambda İfadesi üstteki listede bulunan önceden tanımlamnış delege listesindeki
Func<T1, T2, TResult>
delegesiyle temsil edilebilen ve tamsayı parametreler gönderip boolean değer almak istediğimiz için tam olarak
Func<int, int, bool>
tipindedir.

Lambda İfadesindeki a ve b parametreleri tamsayı tipinde olur, a == b ifadesinin döndürdüğü tip ise boolean türündedir.

Bunun gibi Lambda İfadeleri (Lambda Expressions) yazımızda verdiğimiz temel örnek olan
n => n * n
ifadesi (isimsiz fonksiyonu) herhangi bir tipte bir parametre alıp (n), aldığı değeri kendisiyle çarpıp geri döndürdüğünden 1 parametre alıp 1 sonuç döndüren fonksiyonları temsil etmek için kullanılabilecek
Func<T1, TResult>
tipinde (bu şekilde bir delege ile temsil edilebilecek) bir isimsiz fonksiyondur.

Peki bu kodlar / tanımlamalar ne işe yarar? Üstteki 2 tamsayı alıp 1 boolean döndüren örneği ele alırsak şunu yapabilirsiniz;
bool esitMi = dfonk(5, 6); // esitMi değişkeninin değeri false olur...
Delegede tarif edildiği gibi 2 tamsayı parametre alan ve Boolean sonuç döndüren bir fonksiyon kullanılmıştır. Fonksiyon tanımı yerine biz Lambda İfadesi yazdık (Lambda İfadelerinin de birer fonksiyon olduğunu hatırlayın). Lambda İfademize dikkat ederseniz o da 2 parametre almakta ve bir Boolean sonuç döndürmektedir. Daha sonra ikinci kodda da fonksiyonu temsilci yardımıyla parametre olarak 5 ve 6 değerlerini gönderip çalıştırarak döndürdüğü sonucu Boolean türünden esitMi değişkenine atadık.

Şimdi gelelim bunu ağaç yapısına çevirip Lambda İfadesinin bölümlerine ulaşma işine. Bunun için daha önce belirtildiği gibi System.Linq.Expressions  isim alanındaki Expression sınıfını kullanıyoruz. Tekrar edelim, Expression sınıfı bir Lambda İfadesini ağaç şeklindeki bir veri yapısında temsil etmek için kullanılabilir. Kullanım şöyle;
Expression<Func<int, int, bool>> agacYapisi = (a, b) => a == b;
Artık ifadeyi üstteki şekillerde bahsedilen gövde (Body), sol (Left) ve sağ (Right) bölümlerine ayırabiliriz. Üstteki Lambda İfadesinde gövde;
a == b
sol (left),
a
sağ (right),
b
düğüm tipi (Node Type) ise;
==
olacaktır. Ancak bunları elde edebilmek için yine System.Linq.Expressions  isim alanında bulunan BinaryExpression ve ParameterExpression sınıflarını kullanacağız. BinaryExpession gövde, ParameterExperssion ise sağ ve sol için kullanılır. Öncelikle üstteki tanımlamadan gövde elde edilip BinaryExpression 'a çevrilir. Daha sonra gövdeden sol ve sağ alınarak ParameterExpression 'a çevrilir ve bundan sonra BinaryExpression ve ParameterExpression sınıflarının özellik ve metotları yardımıyla bu bölümlere erişebiliriz.

İşte tam kod;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace Console_TreeLambda
{
class Program
{
static void Main(string[] args)
{
// İfadeyi tanımla ve İfade Ağacına çevir.
Expression<Func<int, int, bool>> agacYapisi = (a, b) => a == b;

// Gövdeyi (body) al (a == b).
BinaryExpression govde = (BinaryExpression)agacYapisi.Body;

// Gövdeden sol (left) tarafı al (a'yı alır).
ParameterExpression sol = (ParameterExpression)govde.Left;

// Gövdeden sağ (right) tarafı al (b'yi alır).
ParameterExpression sag = (ParameterExpression)govde.Right;

// BinaryExpression ve ParameterExpression sınıflarının
// bazı özelliklerini göster bakalım.

Console.Write("Gövde : ");
Console.WriteLine(govde.ToString());

Console.Write("Sol : ");
Console.WriteLine(sol.ToString());

Console.Write("Düğüm tipi (işlem) : ");
Console.Write(govde.NodeType.ToString()); // Equal -> Eşitlik

Console.Write("Sağ : ");
Console.WriteLine(sag.ToString());

Console.ReadKey();
}
}
}
İfade Ağaçları sabittir. Bir İfade Ağacı doğrudan düzenlenemez. Eğer bir İfade Ağacında düzenleme yapmak isterseniz ifadenizdeki düğümleri kopyalayıp yeni bir ifade oluşturmanız ve gerekli değişiklikleri yapmanız gerekir. Bunun için ExpressionVisitor isimli aracı bir sınıf kullanılır. Bu sınıfın kullanımını ile ilgili bir örneği MSDN - How to: Modify Expression Trees adresinde bulabilirsiniz.

Değiştirmeden daha ilginç bulabileceğiniz bir başka konu, yine System.Linq.Expressions isim alanında bulunan Expression sınıfının metotlarıyla yordamsal programlama kullanarak (alt programlar ve fonksiyonlar yardımıyla programlama) ifadeler ve İfade Ağaçları oluşturabilmedir. Bunun için .NET Framework 4.0'da Expression sınıfında bir çok metot vardır. Bu metotların tam listesi MSDN - Expression Class bağlantısında bulunabilir.

Biz yukarıdaki ifadeyi ((a, b) => a == b) oluşturmak için bir örnek verelim. Üstteki ve alttaki örnekleri karşılaştırarak daha iyi anlayabiliriz. Üstteki örnek bir Lambda İfadesini çözümlemekte, alttaki ise aynı ifadeyi oluşturmaktadır.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace Console_BuildExpression
{
class Program
{
static void Main(string[] args)
{
// Tamsayı tipinde bir parametre ifadesi oluştur. İsmi a olsun ve sol değişkenine ata.
ParameterExpression sol = Expression.Parameter(typeof(int), "a");

// Tamsayı tipinde bir parametre ifadesi oluştur. İsmi b olsun ve sag değişkenine ata.
ParameterExpression sag = Expression.Parameter(typeof(int), "b");

// Eşittir düğüm tipi kullanarak sol ve sag'dan bir gövde ifadesi (BinaryExpression
// ile temsil edilir) oluştur.
BinaryExpression govde = Expression.Equal(sol, sag);

// Func<int, int, bool> tipinde (Boolean döndüren ve 2 tamsayı parametre alan)
// bir Lambda İfadesi oluştur ve bunu yine Func<int, int, bool> biçiminde delege
// kullanan İfade Ağacı tanımlayıp, tanımladığın değişkene (alttaki "lammbdaIfadesi") ata.
Expression<Func<int, int, bool>> lambdaIfadesi =
Expression.Lambda<Func<int, int, bool>>(govde,
new ParameterExpression[] { sol, sag });

// Bu şekilde metotlarla oluşturulan ifadelerin çalışabilmesi için Compile
// ile derlenip bir delegeye atanması gerekir.
Func<int, int, bool> temsilci = lambdaIfadesi.Compile();

// (Üstteki 2 satır koddaki tiplerin neler olacağını bilmiyorsanız, kafa veya parmak
// yormak istemiyorsanız "var" ile tanımlayın. Bilindiği gibi var ile değişken
// tanımlandığında derleyici değişkenin tipini derleme zamanında bulur ve
// değişkene uygular. Örneğin : var lambdaIfadesi = ...


// Yukarıdaki metotlarla (a, b) => a == b Lambda İfadesini oluşturduk, çıktıya 1 ve 2
// parametreleriyle fonksiyonunun görünümünü yaz.
// 1 ve 2 parametreleriyle çalıştır.
Console.Write("(1, 2) => 1 == 2 sonucu : ");

// Oluşturulan anonim fonksiyonu temsilci yardımıyla çalıştır ve çıktıya yaz.
Console.WriteLine(temsilci(1, 2).ToString());

Console.ReadKey();

}
}
}
Son olarak bu makaleyi okuduktan sonra Lambda İfadesi yerine çok satırlı C# kodlarını ifadeye çevirmek için daha geniş örnekleri incelemenizi tavsiye ederim. Orjinali şurada bulunan, uzun bir makale olduğundan yorulmayın diye buraya aldığım ve açıklamalarını Türkçe'ye çevirdiğim şu örneği inceleyerek işe başlayabilirsiniz.

Alttaki örnekte bir sayının faktöriyelini hesaplayan fonksiyonun C# ile yazılmış hali ve ikinci olarak Expression sınıfı kullanılarak yazılmış hali var.
using System;
using System.Linq;
using System.Linq.Expressions;

namespace Console_BuildExpression
{
class Program
{
static void Main(string[] args)
{

Console.Write("CSharpFact(5) = ");
Console.WriteLine(CSharpFact(5).ToString());

Console.Write("ETFact(5) = ");
Console.WriteLine(ETFact()(5).ToString());

/// Üstteki ETFact()(5) ifadesini şu şekilde de yazabilirsiniz.
// var ETFactFonksiyon = ETFact();
/// veya fonksiyonun tam tipiyle delege tanımlayarak
// Func<int, int> ETFactFonksiyon = ETFact();
/// daha sonra alttaki gibi çalıştırın.
// Console.WriteLine(ETFactFonksiyon(5).ToString());

Console.ReadKey();

}

// C# Factorial (C# ile faktöriyel)
static int CSharpFact(int value)
{
int result = 1;
while (value > 1) // value > 1 olduğu sürece yap.
{
result *= value--; // result değişkeninin son değerini value ile
// çarp ve value değişkeninin değerini bir azalt
}
return result;
}

// Expression Tree Factorial (İfade ağacı ile faktöriyel)
static Func<int, int> ETFact()
{

// int tipinde, value isimli bir parametre ifadesi oluştur.
ParameterExpression value = Expression.Parameter(typeof(int), "value");

// int tipinde result isimli bir parametre ifadesi oluştur.
// (yerel değişken olarak kullanılacak)
ParameterExpression result = Expression.Parameter(typeof(int), "result");

// Döngüden dışarı çıkmak için goto ile gidilecek bir etiket
// label oluştur.
LabelTarget label = Expression.Label(typeof(int));

// Yöntemin (fonksiyonun) gövdesini oluştur (ifade bloğu başı).
BlockExpression block = Expression.Block(

// Yerel değişkeni gövdeye ekle.
new[] { result },

// Yerel değişkene 1 sabitini ata (result = 1).
Expression.Assign(result, Expression.Constant(1)),

// Bir döngü ekle (while).
Expression.Loop(

// Döngünün içine bir şart bloğu ekle (if ...)
Expression.IfThenElse(

// Şart bloğunun şartı olarak value değişkeninin
// 1'den büyük olması şartını ekle ( ... value > 1)
Expression.GreaterThan(value, Expression.Constant(1)),

// Şart doğruysa result değişkeninin değerini
// value parametresinin son değeriyle çarp ve
// result değişkenine ata (result *= value)
Expression.MultiplyAssign(result,

// Çarpmadan sonra value değişkeninin değerini
// 1 azalt ( ...value--)
Expression.PostDecrementAssign(value)),

// Eğer şart doğru değilse label isimli
// etikete atla / döngüden çık (... else goto label:)
Expression.Break(label, result)

), // Şart bloğu sonu.

// Atlanacak etiket.
label

) // Döngü sonu

); // İfade bloğu sonu.

// İfade Ağacını derle ve bir delege olarak döndür.
return Expression.Lambda<Func<int, int>>(block, value).Compile();

}
}
}

Yazan: 09.04.16 17:34
canora
101,384p 4ü