Şu sıralar .NET dünyasının en popüler konularından biri .NET Compiler Platform(a.k.a Roslyn). .NET Compiler’ın API’i olarak dışarı açılan servisleri ile bayaa taklalar atmak mümkün. Bir önceki yazımda küçük bir giriş yapmıştım hatırlarsanız. .NET Compiler Platform‘u olgunlaşana kadar, biraz da temelleri daha iyi anlamak için “Expression Tree” kavramından, çok derinlere inmeden bahsetmeye çalışacağım. Açıkcası biraz merak uyandırmak, arkalarda işler nasıl oluyor bunları araştırmaya teşvik etmek için bu yazıda çok fazla derinlere girmek istemiyorum. İlerleyen yazılarda zaten kaybolup gideceğiz, baştan söyliyim.Neyse…
Expression Tree, bir çok compiler’ın çalışma mantığında geçerli olan bir kavram aslında. Kodun, ağaç gibi dallar(node) ile temsil edildiği bir veri yapısı diyebiliriz Expression Tree için. Zaten adından da anlaşılıyor. Bu dallar, koddaki her bir parçayı temsil eder. Değişken, metod çağrısı, if-then-else bloğu gibi gibi…
Temel olarak kodların çalışması da, bu ağaç yapısının derlenmesinden sonra oluyor. .NET tarafındaki Expression sınıfı da, bu ağaç yapısının oluşturulmasında ve derlenmesinde kullanılması için bize sunulan bir sınıf. Lambda kavramının .NET tarafında oluşturulması, LINQ ve dinamik runtime(DLR) aslında bu “Expression Tree” yaklaşımı sayesinde, yani Expression sınıfı ile oluyor. Yani basitçe de olsa, çalışan kodda dinamik değişiklikler yapmak, dinamik ifadeler oluşturmak .NET’de zaten mümkündü.
Kod görmeden, ben anlamam diyorsanız aşağıdaki kod örneğine bir göz atalım.
Expression<Func<string, bool>> lambdaExpression = name => name == "Arda";
Func<string, bool> quessMyName = lambdaExpression.Compile();
bool result = quessMyName("Arda");
Console.WriteLine("Sonuç: {0}", result.ToString());
Yukarıdaki kod ile run-time sırasında bir metod oluşturuyoruz ve bu metodu çağırıp, sonucunu konsola yazdırıyoruz. Burada önemki olan generic olan, Expression tipindeki obje. Lambda ifadesi ile name değerinin “Arda”‘ya eşitliliği tanımlanıyor. Bu ifadeyi daha sonra Compile() metodu ile derliyor olduğumuzu düşünebiliriz. Ve artık elimizde quessMyName() adında bir metodumuz olmuş oluyor. Oluşturulduğu context’den dolayı, normal bir metod gibi düşünmek çok doğru olmaz tabi ki.
Expression sınıfı, oldukça geniş bir metod yelpazesine sahip. Kodu yazarken kullanabileceğiniz Expression’ları kolaylıkla ifade edebileceğiniz tüm metodlar var. if-then-else bloğu, döngü oluşturmak için loop bloğu, aritmetik operasyonlar, büyük-küçük ifadeleri gibi Expression sınıfı ile oluşturabilirsiniz. Hepsini tek tek anlatmam mümkün değil açıkcası. Ama biraz daha anlaşılır olması için yine aşağıdaki koda göz atabiliriz.
ParameterExpression value = Expression.Parameter(typeof(int), "value");
LabelTarget returnValue = Expression.Label(typeof(bool));
BlockExpression methodBody = Expression.Block(
Expression.IfThenElse(
Expression.Equal(Expression.Modulo(value, Expression.Constant(2)), Expression.Constant(0)),
Expression.Return(returnValue, Expression.Constant(true)),
Expression.Return(returnValue, Expression.Constant(false))),
Expression.Label(returnValue, Expression.Constant(false))
);
Func<int, bool> IsEven = Expression.Lambda<Func<int, bool>>(methodBody, value).Compile();
var result = IsEven(6);
Console.WriteLine("Sonuç: {0}", result.ToString());
Yukarıda, run-time’da yine bir metod oluşturuyoruz. Bu metod ile bir sayının çift mi ya da tek mi olduğunu kontrol ediyoruz. Basit bir aritmetik işlem aslında. { } ifadelerine denk gelen, Block() metodu içinde, teker teker Expression’larımızı oluşturuyoruz. Önce IfThenElse() ile parametre olarak alacağımız sayının %(Module) mod’unu alıp, bunun 0 şeklinde bir sabit değişkenle eşitliğini kontrol ediyoruz. Eğer eşitlik doğru ise, Return() ifadesi ile true dönüyoruz, eğer eşitlik doğru değil ise yine Return() ifadesi ile false dönüyoruz.
Daha sonra IsEven olarak bir değişken belirleyip bu ifadeyi, Compile() metodu ile derliyoruz(!!! yani sayılır). Bu kod bloğunu yazdığımız içerikte artık IsEven() şeklinde bir metodumuz, -ki aslında bir delege, kullanıma hazır oluyor.
Bu kodu çalıştırdığımızda methodBody‘yi Debug yaparak kontrol ettimizde, DebugView özelliğinde aşağıdaki gibi biraz daha tanıdık bir ifade görüyor olacağız.
Fark etmiş olduğunuz gibi, Expression sınıfı ile yazdığımız ifadeler, biraz daha aşina olduğumuz bir syntax ile karşımızda.
Expression Tree’leri, .NET Framework’ünü genişletmek amacıyla, dinamik olarak kodda değişiklikler ve eklemeler yapmak için kullanabiliriz. Açıkcası ilk başlarda çok zorlanabilirsiniz. Hem Expression’ları oluşturmak, hem de nasıl çalıştıklarını anlamak ilk başlarda oldukça zorlayacaktır. Ancak kavradıktan ve asıl önemlisi tüm Expression metodlarına hakim olduktan sonra bir çok problemi kolaylıkla çözebilecek duruma geleceksiniz.
Şimdilik bu kadar…Bir ara biraz daha derinlere ineriz.