Burak Selim Senyurt(MVP)
Matematik Mühendisi bir .NET Severin Yazıları...

Object vs Dynamic

Perşembe, 1 Nisan 2010 00:10 by bsenyurt

Merhaba Arkadaşlar,

Ayrıntılar detaylarda saklıdır. Bu cümleyi çok severim. Sevdiğim özlü sözler arasındadır. Gerçel bir nesnenin ne kadar kaliteli olduğunu anlamak için detaylarına bakmak gerekir. İşçiliğine, kullanılan malzemeye, malzemelerin uyumuna vs...Hatta benzer diğerleri ile olan kalite farkını anlamak için bile. Çok doğal olarak yazılım dünyasında da bir takım konuların anlaşılabilmesi, kavranabilmesi, benzerleri ile olan farklarının irdelenebilmesi için mutlaka detaylara bakmak, ama sıkılmadan bakmak gerekir. Aynen bu günkü yazımızda yapacağımız gibi.

Bu yazımızda Dynamic Language Runtime kullanımında büyük öneme sahip olan dynamic ile .Net Framework' ün ilk çıktığı zamandan beri var olan Object tipi arasındaki farklılıkları görmeye çalışacağız. Bunun için kod tarafında biraz daha detaya girmemiz gerekecek. Çok derin değil belki de ama aradaki farklılıkları çıkartabilmek adına önemli detaylar. Başlamadan önce örneklerimizi Visual Studio 2010 Ultimate RC sürümünde geliştirdiğimizi ve ilerleyen sürümlerde farklılıklar olabileceğini hatırlatmak isterim.

Öncelikli olarak işe aşağıdaki basit kod parçası ve Object tipini ele alarak başlayalım.

Case 1;

object pi = Math.PI;
Console.WriteLine(pi.GetType().ToString());

İlk satırda Math.PI sabit değerinin object tipine atandığını görüyoruz. Aslında bilinçsiz olarak bir tür dönüşümü söz konusu(Implicitly Type Casting). Burada herhangibir sıkıntı yok. pi.GetType satırının çalışma zamanı çıktısı ise System.Double olmalıdır. Nitekim eşitliğin sağ tarafından gelen Math.PI, double tipinden olduğu için pi isimli object tipi üzerinden ele alınsa da kendi tipini taşımış olmaktadır.

Şimdi kodumuzu aşağıdaki gibi değiştirdiğimizi düşünelim.

Case 2;

object pi = Math.PI;
object square = pi * 10 * 10;

Bu kez object tipinden olan pi ile iki sayısal değeri(ki bunlarda int tipindendir) matematiksel bir işleme tabi tutup sonucu yine bir object tipine atamaya çalışıyoruz. Ancak kodu derlediğimizde Compiler' ın bir derleme zamanı hata mesajı ürettiğini görürüz.

Hımmm...Aslında bu son derece doğal bir sonuç. Nitekim derleme zamanında pi değişkeninin tipi object ve biz object tipi ile int tiplerini işleme sokmaya çalışıyoruz. Burada çözüm, dönüştürme işlemini bilinçli olarak yapmaktan ibaret(Explicitly Type Casting). Yani kodu aşağıdaki gibi düzenlemekten;

Case 3;

object pi = Math.PI;
object square = (double)pi * 10 * 10;

Dikkat edileceği üzere pi değişkeni bilinçli olarak double tipine dönüştürülmüş durumdadır. Aslında az önceki senaryomuzda pi' nin taşıdığı tipin double olduğunu görmüştük. Yine de başka tipler ile işleme tabi tuttuğumuzda derleme zamanı hatası ile karşılaşmaktan kurtulamadık. Çünkü pi değişkeni double tipten değer taşıyan bir object idi aslında.

Şimdi durumu biraz daha entersan bir hale getireceğiz. Aşağıdaki kod parçasını göz önüne alın ve kodu kafanızda derleyip hata olup olmadığını söyleyin. Sonrasında ise çalışma zamanında bir hata oluşup oluşmayacağını düşünün ve bir karar daha verin. En sonunda ise bunu, konu ile ilgili yakın arkadaşlarınızla tartışın Wink

object pi = Math.PI;
object square = (int)pi * 10 * 10;

Derleme zamanında herhangibir hata mesajı alınmayacaktır. Ancak çalışma zamanına(Runtime) geçtiğimizde aşağıdaki ekran görüntüsünde yer alan istisna(Exception) ile karşılaşırız.

Upsss!!!

Sorun şudur; pi değişkeni object tipinden tanımlanmıştır ve eşitliğin sağ tarafına göre double tipinden bir değer taşımaktadır. Ancak bu tip cast operatörüne göre int tipine dönüştürülmeye çalışılmaktadır. Oysaki çalışma zamanının burada beklediği tam olarak double tipine dönüştürme işlemidir.

Dolayısıyla object tipini kullandığımız bu senaryoda matematiksel işlemlerin yapılabilmesi için, mutlaka doğru tipe dönüşüm gerçekleştirilmelidir. Eğer bir dönüştürme işlemi yapmassak, derleme zamanında hata mesajı alırız. Ancak tip dönüşümü yaparkende koltuğumuzda rahat edemeyiz, çünkü yanlış tipe dönüştürme işlemleri çalışma zamanı istisnaları ile cezalandırılır. Buna göre gerçekten beklenen tipe dönüşüm işlemi sağlanmalıdır.

Gelelim yeni gözdemiz olan Dynamic tipe. Yukarıdaki senaryoları bire bir, ama bu kez dynamic tipini kullanarak değerlendireceğiz.

dynamic pi = Math.PI;
Console.WriteLine(pi.GetType().ToString());

Bu kez eşitliğin sol tarafında dynamic tipi vardır. object tipinin kullanıldığı örnektekine(Case 1) benzer olaraktan pi değişkeni yine double tipinden bir değer taşımaktadır. Çalışma zamanının üreteceği sonuçta aynı olacaktır. Ancak aşağıdaki kod parçasını göz önüne aldığımızda,

dynamic pi = Math.PI;
dynamic square = pi * 10 * 10;

object tipinin kullandığımız örneğe(Case 2) baktığımızda derleme zamanında bir hata aldığımızı hatırlıyoruzdur sanırım. Oysaki dynamic tipi derleme zamanı ile ilgilenmemektedir. Çalışma zamanında ise pi' yi gereken tipte ele almaktadır. Zaten object tipini kullandığımız üçüncü senaryomuzu ele almamıza da gerek kalmamıştır. Wink

Buna göre şöyle bir sonuca varabiliriz. Dynamic tipin kullanıldığı hallerde derleyicinin(Compiler) derleme zamanında tip tahmini yapmasına gerek yoktur. Bu çözümleme işi çalışma zamanında yapılmaktadır.

Tabi bu sonuçlara göre "her yerde dynamic tip kullanalım mı?" sorusu da gündeme gelir. Ancak "metodlara parametre aktarımlarında dynamic kullanımının kodun kırılmasına neden olması söz konusu olabilir mi?" sorusu daha da önemlidir. Şimdi bu durumu ele almaya çalışalım. İşte kodlarımız;

            dynamic R = "on iki";
            Calculate(R);

            #endregion

            #endregion

        }

        static double Calculate(double r)
        {
            return Math.PI * r * r;
        }

Bu kod parçasında kodu kırmaya yönelik olarak bir hamle yapıldığı düşünülebilir. Calculate metodu double tipinden bir değer beklemektedir. Biz ise dynamic olarak tanımladığımız R değişkenine string bir değer atayarak parametre gönderme işlemini gerçekleştirmekteyiz. Dynamic kullanımına göre derleme zamanında bir hata mesajı alınmaması normaldir. Benzer şekilde çalışma zamanında gelen string tipinin, double tipe otomatik olarak dönüştürüleceğini de düşünebiliriz. Eğer böyle olsaydı, kodun dynamic tipi yardımıyla kolayca kırılabileceği sonuçlarına varabilirdik. Şükür ki çalışma zamanında aşağıdaki hata mesajı ile cezalandırılırız. Laughing

Ancak,

dynamic R=12;

atamasını yaparsak herhangibir sorun ile karşılaşmayız. Çünkü Calculate metodunun beklediği(veya taşıyabildiği) tipte bir değişkenin gönderilmesi sağlanmaktadır.

Sonuç olarak bir metoda dynamic tipte veri atayabiliyor olsakta bu, metoda her tipten değeri aktarabileceğimiz anlamına gelmemektedir. Gerçekten metodun beklediği veya kabul edebileceği türden bir değişkenin atanması şarttır.

Son senaryomuzu object tipi ile düşündüğümüzdeyse yine başta açıklanan 3 vakanın gerçekleşeceği görülecektir. Gelin bu durumları bir kere daha inceleyelim. Kodu ilk etapta aşağıdaki gibi düzenleyelim.

object R = 12.1;
Calculate(R);

Bu durumda daha derleme zamanında hata mesajı alırız. Aşağıdaki şekilde olduğu gibi.

Dolayısıyla tip dönüşümü yapmamız şarttır. Öyleyse yapalım. Laughing

object R = 12.1;
Calculate((int)R);

Derleme zamanında hata yok. Süper...Ama oda ne? Çalışma zamanında yine hata aldık. Undecided

Tahmin edileceği üzere object tipi kullanıldığından çalışma zamanında tam olarak double tipinden bir değer taşınması beklenmektedir. 12.1 double olarak ifade edilmesine rağmen int tipine yapılan dönüşüm geçersizdir(Dynamic tipin kullanımının tam aksine). Dolayısıyla kodun aşağıdaki gibi düzenlenmesi gerekir.

object R = 12.1;
Calculate((double)R);

Bu durumda çalışma zamanında her hangibir hata mesajı alınmadan ilerlenebilecektir.

Görüldüğü üzere derinlerde, dynamic ve object kullanımları arasında belirgin farklılıklar bulunmaktadır. Bunların sebepleri aşikardır. Böylece geldik bir yazımızın daha sonuna. Tekrardan görüşünceye dek, hepinize mutlu günler dilerim.

DynamicVsObject_RC.rar (20,73 kb) [Örnek Visual Studio 2010 Ultimate RC sürümü üzerinde geliştirilmiş ve test edilmiştir]

LINQ Maceralarım

Perşembe, 10 Nisan 2008 01:36 by bsenyurt

Language INtegrated Query(LINQ) mimarisi sayesinde CLR nesneleri(Common Language Runtime Objects) üzerinden SQL tarzı sorgu ifadeleri yazılabilmektedir. Hatta LINQ mimarisi, SQL veritabanı(LINQ to SQL) ve XML (LINQ to XML) kaynakları üzerindede kullanılabilmektedir. Özellikle IEnumerable<T> arayüzünü uyarlayan tiplere ait nesne örnekleri için, Select, Where, GroupBy, Sum, Avg, Distinct ve daha pek çok bilinen sorgulama metodu uygulanabilmektedir. LINQ içerisinde yer alan imkanlar göz önüne alındığında, .Net Framework 1.1, 2.0 ve 3.0 ile geliştirilmiş pek çok projenin .Net 3.5' e aktarılarak bu olanaklardan yararlanabilmeleri için gerekli geçiş hazırlıklarının ciddi anlamda düşünüldüğüde ortadadır. Üstelik Visual Studio 2008, getirdiği çoklu framework desteği sayesinde .Net Framework 2.0, 3.0 ve 3.5 arasındaki geçişlerin kolayca yapılabilmesini sağlamaktadır. Bu gibi konular göz önüne alındığında bir geliştirici olarak LINQ' in daha önceki kod parçalarında kullanılabileceği yeni yerlerde merak konusu haline gelmektedir. İşte bu makalemizde, LINQ sorgularını farklı kod parçalarında kullanmaya çalışıyor olacağız.

LINQ mimarisinin kullanılabileceği alanlar göz önüne alındığında, Reflection(Yansıma), IO(Dosya giriş/çıkış), Bağlantısız Katman(Disconnected Layer) sadece bir kaç basit alan olarak ön plana çıkmaktadır. Ancak bu alanlar pek çok uygulamada önemli görevler üstlenmektedir. Söz gelimi yansıma teknikleri ile IDE geliştirilmesi(Visual Studio benzeri), Plug-In tabanlı uygulamalar yazılması, nitelik(Attribute) bazlı olacak şekilde çalışma ortamının organize edilmesi(özellikle deklerafit programlama tekniklerinde) gibi işlevsellikler ön plana çıkmaktadır. Dosyalama işlemleri en basit anlamda resim işleme programlarından, XML ayrıştırma uygulamalarına kadar pek çok alanda kullanılmaktadır. Çok doğal olarak Ado.Net mimarisine göre geliştirilen pek çok uygulamada bağlantısız katman nesneleri görülebilmektedir. Sadece bu konular bile göz önüne alındığında bazı kod ihtiyaçları için diziler, döngüler ve koşullu ifadelerin çok sık kullanıldığıda göze çarpmaktadır. Ancak LINQ sorguları sayesinde bu işlemler çok daha basit bir şekilde gerçekleştirilebilir. Elbetteki genişletme metodlarının üstlendiği yük çerçevesinde söz konusu döngülerin, koşullu ifadelerin ortadan kalkması gibi bir durum mümkün değildir. Ancak kodun çok daha etkin bir şekilde ve bir sorgulama diline yatkın olaraktan geliştirilmesi önemli bir avantajdır.

Not : Bilindiği gibi LINQ sorgularında yer alan anahtar kelimeler(keywords) aslında arka planda birer genişletme metoduna(Extension Methods) karşılık gelmektedir. Bu metodlar söz gelimi basit bir arama işlemi için gereken döngüsel veya koşullu ifadeleri kapsülleyerek geliştiricinin üzerinden almaktadır. Bu sayede geliştirici SQL diline yatkın bir şekilde sorgular yazabilmekte ve kodun daha etkili, ölçeklenebilir, anlaşılır bir şekilde geliştirilmesine odaklanabilmektedir.

Dilerseniz hiç vakit kaybetmeden örneklerimize başlayalım. İlk olarak dosyalama işlemlerini göz önüne alarak ilerleyebiliriz. Söz gelimi, herhangibir klasör içerisinde yer alan Jpg uzantılı dosyalardan boyutu 1000 kb üzerinde olanların tespit edilmesini istediğimizi düşünelim. Bu işlemi VS 2008 tabanlı bir Console Uygulamasında aşağıdaki kod parçası ile gerçekleştirebiliriz.

string klasorAdresi= @"C:\Documents and Settings\BurakSenyurt\My Documents\My Pictures\Google Pictures\";
DirectoryInfo dInfo = new DirectoryInfo(klasorAdresi);
var resimDosyalari = from fInfo in dInfo.GetFiles()
                                    where fInfo.Extension == ".jpg" && fInfo.Length >= 1000 * 1024
                                        select new
                                                    {
                                                        fInfo.Name
                                                        ,fInfo.Length
                                                        ,fInfo.CreationTime
                                                    };
foreach (var dosya in resimDosyalari)
    Console.WriteLine(dosya);

DirectoryInfo sınıfının GetFiles metodu FileInfo tipinden bir dizi döndürmektedir. Bu dizi bir Array tipi olduğu için LINQ ile birlikte gelen genişletme metodlarını(Extension Methods) kullanabilmektedir. Dolayısıyla LINQ ifadesi içerisinde from, select, where gibi anahtar kelimeler kolay bir şekilde ele alınabilmektedir. FileInfo dizisi üzerinden dosya uzantısı(Extension) .jpg ve uzunluğu(Length) 1000 Kb üzerinde olanlar tespit edilirken aynı zamanda isimsiz bir tip(Anonymous Type) üretimide gerçekleştirilmekte ve ilgili dosya için ad(Name), uzunluk(Length) ve oluşturulma zamanı(CreationTime) bilgilerinin yer aldığı yeni bir nesne örneği oluşturulmaktadır. Program kodunun çıktısı örnek klasör için aşağıdaki gibidir.

 

Eğlenceli değil mi? Öyleyse devam edelim. Diyelimki dosyalama işlemleri ile ilgili olaraktan şöyle bir ihtiyacımız oldu;Bir klasör içerisindeki dosyaları tiplerine göre gruplayıp, her grup içerisinde kaçar adet dosya bulunduğunu öğrenmek istiyoruz. Bu kodun LINQ ifadesini yazmadan önce, LINQ olmadan nasıl geliştirilebileceğini düşünmenizi öneririm. LINQ ile bu sorgu aşağıdaki kod parçasında olduğu gibi gerçekleştirilebilir.

string adres = @"C:\Windows\";
DirectoryInfo dInfo = new DirectoryInfo(adres);
var dosyaGruplari = from fInfo in dInfo.GetFiles()
                                    group fInfo by fInfo.Extension into grp
                                        select new
                                                    {
                                                        Uzanti = grp.Key,
                                                        Toplam = grp.Count()
                                                    };

foreach (var dosyaGrubu in dosyaGruplari)
    Console.WriteLine(dosyaGrubu.ToString());

Bu seferki LINQ ifadesinde group by kullanımı söz konusudur. Group By sayesinde aynen SQL' de olduğu gibi gruplama işlemi nesneler üzerinde yapılabilmektedir. Örnekte Windows klasörü altındaki dosyalar FileInfo tipinin Extension özelliğine göre gruplanmaktadır. Sonrasında ise gruplanan koleksiyon üzerinden Count genişletme metodu kullanılmakta ve her bir tip grubu için kaçar dosya olduğu hesaplanmaktadır. İlk örnekte olduğu gibi yine isimsiz tip(Anonymous Type) kullanılarak dosya grubuna ait uzantı ve toplam dosya sayısı bilgileri elde edilmektedir. Sonuç olarak uygulamanın ekran çıktısı aşağıdaki gibi olacaktır.

Şimdide herhangibir klasördeki jpg uzantılı dosyalardan L harfi ile başlayanları boyutlarına göre tersten sıralayarak elde etmek istediğimizi düşünelim. LINQ kullanmadığımız takdirde bize en çok sorun çıkartacak noktalardan biriside tersten sıralama işlemi olacaktır. Bunu sağlamak için doğal olarak FileInfo dizisi üzerinden ters sıralama algoritması uygulanması gerekir. Oysaki LINQ ifadeleri ile bu işlem için gerekli kod parçası aşağıdaki gibi kolayca geliştirilebilir.

string klasorAdresi = @"C:\Documents and Settings\BurakSenyurt\My Documents\My Pictures\Google Pictures\";
DirectoryInfo dInfo = new DirectoryInfo(klasorAdresi);

var dosyalar=from fInfo in dInfo.GetFiles()
                        where fInfo.Extension==".jpg" && fInfo.Name[0]=='L'
                            orderby fInfo.Length descending
                                select new
                                                {
                                                    fInfo.Name,
                                                    fInfo.Length
                                                };

foreach (var dosya in dosyalar)
    Console.WriteLine("{0} \t{1}",dosya.Length,dosya.Name);

Bu kez orderby anahtar kelimesi(ki bu arka planda OrderBy genişletme metoduna dönüştürülmektedir) kullanılarak dosyaların boyutlarına göre tersten sıralanması sağlanmıştır. Sonuç olarak kodun ekran çıktısı aşağıdakine benzer olacaktır.

LINQ sorguları dosyalama işlemleri dışında özellikle reflection(yansıma) tarafındada etkili bir şekilde kullanılabilir. Yazımızın bundan sonraki kısmındada yansıma teknikleri içerisinde LINQ ifadelerini örnekler üzerinde ele almaya çalışacağız. Öncelikli olarak Process' lerden başlamak taraftarıyım. Bilindiği üzere .Net uygulamaları sistem üzerinde açılan Process' ler içerisinde ayrı uygulama alanları(Application Domains) altına dahil edilirler. Hatta bu uygulama alanları kendi içlerinde, birden fazla(en az bir tane olmak üzere) Thread' ede sahip olabilirler. Sistem üzerinde çalışan Process' lerin yada o anda çalışmakta olan güncel Process' in bilgilerini almak için Process sınıfının farklı metodları bulunmaktadır. Bizimde aklımıza gelen soru şudur; acaba sistem üzerinde çalışmakta olan Process' ler içerisinde sadece tek bir Thread' e sahip olanlar hangileridir. Nitekim bilindiği üzere bazı Process' ler kendi içlerinde birden fazla Thread içermektedir. Bu amaçla aşağıdaki gibi bir kod parçası geliştirilebilir.

var processes = from prc in Process.GetProcesses()
                            where prc.Threads.Count == 1
                                orderby prc.ProcessName descending
                                    select new
                                                {
                                                    prc.ProcessName
                                                    , prc.PagedMemorySize64
                                                };
foreach (var process in processes)
    Console.WriteLine(process.ToString());

Process sınıfının static GetProcesses metodu ile o anda sistemde çalışmakta olan Process' ler elde edilmektedir. Sonrasında where anahtar kelimesi ile Threads özelliği üzerinden Count değeri kontrol edilir. 1 olanlar adlarına(ProcessName) göre orderby anahtar kelimesinden yararlanılarak tersten sıralanacak şekilde yeni bir isimsiz tip içerisinde toplanırlar. Bu isimsiz tip(Anonymous Type) örnek olarak Process' in adı(ProcessName) ve sayfalanmış bellek boyutu(PagedMemorySize64) değerlerini içermektedir. Sonuç olarak kodun çıktısı, çalışılan sistem üzerinde aşağıdaki gibi olmuştur.

Reflection ile başlamışken hızımızı kesmeyelim ve yeni bir sorgu ile devam edelim. Bu kez şöyle bir ihtiyacımız var; bir assembly' ın referans ettiği assmebly' lar içerisinden versiyonu .Net Framework 2.0 olmayanları bulmak istiyoruz. Bu tip bir durumda var olan Assembly' ın yüklenmesi ve referans ettiği Assembly' ların GetReferencedAssemblies metodu ile çekilmesi gerekir. Ne tesadüftürki GetReferencedAssemblies metodu AssemblyName tipinden bir dizi döndürmektedir. Dolayısıyla bu dizi üzerinden LINQ ifadeleri kullanılabilmesi olasıdır. Söz konusu ihtiyaç için aşağıdaki gibi bir kod parçası düşünülebilir.

AssemblyName[] result1 = Assembly.LoadFrom(@"C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\System.EnterpriseServices.dll")
.GetReferencedAssemblies();
var framework2Olmayanlar = from asmb in result1
                                                where asmb.Version != new Version(2, 0, 0, 0)
                                                    select new
                                                                {
                                                                    AssemblyAdi = asmb.FullName
                                                                    ,IslemciMimarisi = asmb.ProcessorArchitecture
                                                                    ,HashAlgoritması = asmb.HashAlgorithm
                                                                };

foreach (var a in framework2Olmayanlar)
    Console.WriteLine(a.ToString());

Örnek olarak System.EnterpriseServices.dll assembly' ı kullanılmaktadır. Sorgu içerisinde dikkat edilecek olursa GetReferencedAssemblies metodu ile elde edilen sonuç kümesi üzerinden çekilen her bir AssemblyName nesnesinin Version özelliğine bakılmaktadır. Sonrasında ise yine bir isimsiz tip kullanılarak sadece Assembly' ın adı(FullName), işlemci mimarisi(ProcessorArchitecture) ve hash algoritması(HashAlgorithm) değerleri toplanmaktadır. Örneğin ekran çıktısı aşağıdaki gibi olacaktır.

LINQ sorguları içerisinde bazı yerlerde harici metodlarında çağırılması mümkündür. Söz gelimi bir koşul kontrolü için iterasyonun o andaki nesnesinin denetlenmesi gerektiği durumlarda harici metod çağrıları gerekebilir. Örneğin dll uzantılı dosyalar ile dolu bir klasör içerisinde .Net Assembly' ı olarak yüklenebilenlerin tespit edilmesini istediğimiz düşünelim. Böyle bir senaryoda Assembly sınıfının static LoadFrom metodu oldukça işe yarayacaktır. Nitekim söz konusu dll herhangibir nedenle yüklenebilen bir Assembly değilse çalışma zamanı istisnası(Runtime Exception) oluşacaktır. Aşağıdaki kod parçası bu durumu analiz etmek için geliştirilmiştir.

class Program
{
    static void Main(string[] args)
    {
        string path = @"C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727";
        DirectoryInfo klasor = new DirectoryInfo(path);
        var assemblyOlanlar = from dosya in klasor.GetFiles("*.dll")
                                            where Yuklenebildinmi(dosya.FullName)
                                                select dosya;

        foreach (var asmb in assemblyOlanlar)
            Console.WriteLine(asmb.FullName);
    }

    static bool Yuklenebildinmi(string assemblyAdresi)
    {
        try
        {
            Assembly asmbly = Assembly.LoadFrom(assemblyAdresi);
            return true;
        }
        catch
        {
            return false;
        }
    }
}

GetFiles metodu ile dll uzantılı FileInfo dizisi elde edildikten sonra her bir eleman için Yuklenebildimi isimli bir metod ile denetleme işlemi gerçekleştirilmektedir. Yuklenebildimi isimli fonksiyon, Assembly.LoadFrom metodu işe yarıyorsa true değerini, yaramıyorsa false değerini döndürmektedir. Buna göre true değeri dönen dosyaların yüklenebilen assembly' lar olduğu sonucuna varılmaktadır. Uygulamanın çalışma zamanındaki görüntüsü aşağıdakine benzer olacaktır.

Yine assembly' lar üzerinden LINQ sorguları yazmaya devam edelim. Örneğin bir assembly içerisinden dışarıya sunulan harici tipler göz önüne alınsın. Burada işin içerisine gruplama fonksiyonelliğinide katarak, hangi isim alanı(namespace) içerisinden kaç adet tipin dışarıya sunulduğu bilgiside elde edilebilir. Bu işi gerçekleştirmek için örnek olarak aşağıdaki gibi bir kod parçası göz önüne alınabilir.

Assembly systemAsmb = Assembly.LoadFrom(@"C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\System.Web.dll");
var hariciTipler = from t in systemAsmb.GetExportedTypes()
                            group t by t.Namespace into ng
                                orderby ng.Key descending
                                    select new
                                                {
                                                    IsimAlaniAdi = ng.Key,
                                                    TipSayisi = ng.Count()
                                                };
foreach (var hariciTip in hariciTipler)
    Console.WriteLine("{0} isim alanından {1} tip vardır", hariciTip.IsimAlaniAdi, hariciTip.TipSayisi.ToString());

Bu LINQ sorgusunda GetExportedTypes metodu yardımıyla örnek olarak System.Web.dll assembly' ı içerisinden dışarıya sunulmakta olan harici tiplerin listesi Type türünden bir dizi olarak elde edilmektedir. Sonrasında ise her tip, Namespace özelliğinin değerine göre group anahtar kelimesi yardımıyla ng isimli değişken altında gruplanmaktadır. Gruplanan veriler sonucu elde edilen liste Namespace adlarına göre tersten(descending) sıralanmaktadır. Bu noktada devreye orderby anahtar kelimesi girmektedir. Elde edilen listeden isim alanı adları Key özelliği ile ve tip sayılarıda Count genişletme metodu ile çekilerek yeni bir isimsiz tip altında toplanmaktadır. Kod parçasının çalışmasının sonucu oluşan örnek ekran çıktısı ise aşağıdaki gibidir.

Peki herhangibir assembly içerisinde kaç farklı isim alanı olduğunu bulmak istersek. Normal şartlarda bu işlem için isim alanı adlarını çektikten sonra bir fonksiyonellik geliştirilmesi gerekmektedir. Oysaki LINQ ile birlikte genen Distinct genişletme metodu sayesinde söz konusu işlem aşağıdaki kod parçasında olduğu gibi kolayca gerçekleştirilebilir.

Assembly systemAsmb = Assembly.LoadFrom(@"C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\System.Xml.dll");
var isimAlanlari = (from t in systemAsmb.GetTypes()
                                select t.Namespace).Distinct();
Console.WriteLine("\n{0} assembly' ı içerisinde {1} farklı isim alanı adı vardır", systemAsmb.FullName,isimAlanlari.Count()-1);
foreach (var isimAlani in isimAlanlari)
    Console.WriteLine(isimAlani);

Burada dikkat edilmesi gereken noktalardan biriside Distinct işlevselliğinin bir metod olarak select sorgusunun arkasından kullanılmasıdır. Buna ek olarak Count genişletme metodu ilede farklı isim alanlarının sayısı çekilmektedir. Örnekte yer alan System.Xml.dll assembly' ı için ilgili sonuçlar aşağıdaki gibi olacaktır.

Reflection ile ilişkili olarak LINQ sorgularını kullanacağımız son bir örnek ile devam edelim. Bu sefer bir assembly içerisinde yer alan tiplerin toplam sayılarını türedikleri base type' lara göre gruplayarak elde etmeye çalışıyor olacağız. Bu amaçla, Type sınıfının BaseType özelliği gruplama işleminde kullanılabilir. Söz gelimi System.dll assembly' ı içerisindeki tipleri BaseType özelliklerinin değerlerine göre gruplamak istersek aşağıdaki kod parçası yeterli olacaktır.

Assembly systemAsmb = Assembly.LoadFrom(@"C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\System.dll");
var tipler = from m in systemAsmb.GetTypes()
                    group m by m.BaseType into grp
                        select new
                                    {
                                        grp.Key
                                        ,Toplam = grp.Count()
                                    };
foreach (var tip in tipler)
    Console.WriteLine("{0} \t{1}", tip.Key, tip.Toplam.ToString());

Bir önceki örnektekine benzer olacak şekilde yine group by fonksiyonu kullanılmaktadır. Sonuç olarak üretilen isimsiz tip içerisinde BaseType adı ve toplam tip sayısı değerleri yer almaktadır. Örnek kodun çalışma zamanındaki ekran çıktısı aşağıdakine benzer olacaktır.

LINQ(Language INtegrated Query) ifadeleri pek çok dizi tipi ve koleksiyon üzerinde etkin bir şekilde kullanılabildiğinden, akla gelen konulardan biriside görsel uygulamalarda yer alan Controls koleksiyonlarıdır. Bir windows uygulamasında yada web uygulamasında Container görevi üstlenen ve bu sebepten Controls koleksiyonuna sahip olan nesnel topluluklar üzerinde de LINQ sorguları çalıştırılabilir. Bunu basit bir örnek üzerinden inceleyebiliriz. Söz gelimi aşağıdaki ekran görüntüsüde yer alan bir Windows Formumuz olduğunu düşünelim.

Amacımız şimdilik bu form üzerinde hangi tipte kontroller bulunduğunu göstermek. Bu amaçla basit olarak aşağıdaki gibi bir kod parçası yeterli olacaktır.

private void button2_Click(object sender, EventArgs e)
{
    lstSonuclar.Items.Clear();

    IEnumerable<Control> kontroller=Controls.Cast<Control>();

    var farkliTipler = (from kontrol in kontroller
                                select kontrol.GetType()).Distinct().OrderBy(k => k.Name);

    foreach (Type farkliTip in farkliTipler)
        lstSonuclar.Items.Add(farkliTip.Name);
}

Bu kod parçasıda belkide en önemli noktalardan biriside Controls özelliği üzerinden kullanılan Cast<T> genişletme metodudur. Cast<T> metodu kullanılmadığı takdirde Controls özelliği üzerinden Select, Where, GroupBy gibi LINQ sorgularında önem arz eden fonksiyonelliklere erişilemediği görülür. Cast<T> metodunun buradaki görevi Controls koleksiyonu içerisindeki bileşenleri, parametre olarak verilen generic tipe dönüştürerek IEnumerable arayüzünün taşıyabileceği bir nesne topluluğu referansı halinde üretmektir. Böylece LINQ sorguları için gerekli fonksiyonellikler elde edilebilmektedir. Windows formu üzerindeki görsel bileşenler Control sınıfından türemektedir. Bu sebepten Cast<T> metodunun generic parametresi Control tipindendir. Bu dönüştürme işleminin ardından Distinct ve OrderBy genişletme metodlarınında yer aldığı bir LINQ sorgusu çalıştırılması mümkün olmaktadır. Select sorgusunda, GetType metodunun kullanılmasının sebebi tiplerin benzersiz şekilde ele alınmak istemesidir. Sonuç olarak uygulamanın çalışma zamanındaki ekran çıktısı aşağıdakine benzer olacaktır.

Görüldüğü gibi form üzerinde hangi tipten kontrollerin var olduğu listelenmektedir. Yeni bir sorgu ile devam edelim. Bu sefer form üzerindeki kontrolleri tiplerine göre gruplayıp her bir tipten kaç adet olduğunu bulmak istediğimizi düşünelim. Bu basit gruplama işleminin kodu aşağıdaki gibi geliştirilebilir.

private void button2_Click(object sender, EventArgs e)
{
    lstSonuclar.Items.Clear();

    IEnumerable<Control> kontroller=Controls.Cast<Control>();

    var farkliTipler = from kontrol in kontroller
                                group kontrol by kontrol.GetType() into grp
                                    select new
                                                {
                                                    KontrolAdi=grp.Key
                                                    ,Toplam=grp.Count()
                                                };

    foreach (var farkliTip in farkliTipler)
        lstSonuclar.Items.Add(String.Format("{0} : {1}",farkliTip.KontrolAdi,farkliTip.Toplam.ToString()));
}

Bu sefer gruplama işlemi Control tipinin GetType metoduna göre yapılmaktadır. Uygulama kodunun ekran çıktısı aşağıdaki gibi olacaktır.

Cast<T> metodu doğrudan LINQ genişletme metodlarının kullanılamadığı pek çok senaryoda ele alınabilir. Söz gelimi aşağıdaki kod parçası çok basit olarak Application Log altındaki girişlerden programın çalıştırıldığı gün içerisinde üretilenlerin çekilmesini sağlamaktadır.

EventLog logs = new EventLog("Application", ".", "");
IEnumerable<EventLogEntry> entries = logs.Entries.Cast<EventLogEntry>();

var girisler = from entry in entries
                    where entry.TimeGenerated.Day == DateTime.Now.Day
                        select new
                                    {
                                        entry.Category,
                                        entry.CategoryNumber,
                                        entry.EntryType,
                                        entry.TimeGenerated
                                    };

foreach (var giris in girisler)
    Console.WriteLine(giris.ToString());

Bu kod parçasında kullanılan Cast<T> genişletme metodu geriye, IEnumerable<EventLogEntry> arayüzü(interface) tarafından taşınacak bir nesne topluluğu referansı döndürmektedir. IEnumerable<T> arayüzüne ulaşıldığı içinde LINQ sorgusu kolay bir şekilde ele alınmış ve aşağıdaki ekran çıktısının üretilmesi sağlanmıştır.

Cast<T> metodu ile benzer özelliğe sahip bir diğer önemli metodda OfType<T> genişletme metodudur. Bu metod bir nesne topluluğu üzerinde, generic parametre tipine göre filtreleme yapılabilmesini ve geriye LINQ sorgularının uygulanabileceği bir IEnumerable<T> referansı döndürülmesini sağlamaktadır.

Not : Cast<T> metodu ile OfType<T> metodu benzer işlevselliğe sahip görünmekle birlikte arada önemli farklar vardır. OfType<T> metodu temel olarak generic parametre tipine göre bir filtreleme yapmakta iken, Cast<T> metodu generic parametre tipine dönüştürme yapmaktadır. Bu sebepten dönüştürme yapılamayacağı durumlarda Cast<T> metodu, çalışma zamanında InvalidCastException istisnası üretilmesine neden olur. Oysaki OfType<T> metodu bu durumu tamamen görmezden gelir ve diğer nesneden devam eder. OfType<T> kendi içerisinde is anahtar kelimesini kullanarak tip kontrolü yapmaktayken, Cast<T> doğrudan dönüştürme adımını uygular.

Şimdi OfType<T> metodunu örnek bir senaryo üzerinden ele almaya çalışalım. Örneğin uygulamamızda kullandığımız .Net Framework 2.0 ile geliştirilmiş bir kütüphane olsun. Bu kütüphane içerisinde yer alan metodlardan bazılarınında ArrayList gibi tür güvenli olmayan koleksiyonlar döndürdüğünü düşünelim. Referansta bulunan uygulamanın .Net 3.5 tabanlı olduğu düşünülecek olursa, gelen koleksiyon nesneleri üzerinden LINQ sorguları çalıştırılması istenebilir. Bu noktada OfType<T> metodu oldukça işe yarayacaktır. Söz konusu senaryoyo ele almak için aşağıdaki tipi içeren bir sınıf kütüphanesi(Class Library) olduğunu düşünelim.

public class Yardimci
{
    public ArrayList ListeyiAl()
    {
        ArrayList liste = new ArrayList();
        liste.Add("Burak");
        liste.Add("Bili");
        liste.Add("Behçet");
        liste.Add("Necdet");
        liste.Add("Kerim");
        liste.Add("Mayk");
        liste.Add(19.90);
        liste.Add(10);
        liste.Add(true);
        liste.Add(false);
        liste.Add('C');
        return liste;
    }
}

Kod parçasında kasıtlı olarak ArrayList içerisine farklı tipte veriler atılmıştır. Eğerki LINQ sorgusunda bu metoddan dönen değerler içerisinden sadece string tabanlı olanları ele almak istiyorsak, OfType<T> metodunu aşağıdaki kod parçasında olduğu gibi kullanabiliriz.

GenelIslemler.Yardimci yrdm = new GenelIslemler.Yardimci();

var besHarfliler = from nesne in yrdm.ListeyiAl().OfType<string>()
                            where nesne.Length == 5
                                select nesne;

foreach (string nesne in besHarfliler)
    Console.WriteLine(nesne);

OfType<string> metodu buradaki kullanıma göre ListeyiAl fonksiyonundan gelen ArrayList içerisindeki tüm nesnelerde, is kontrolünü yaparak sadece String olanları geriye döndürmektedir. Sonrasında nesnelerin karakter uzunluğu kıyaslanarak 5 ise çekilmektedir. Program kodunun çıktısı aşağıdaki gibi olacaktır.

Yazımızda son olarak .Net Framework 2.0 ile yazılmış ve DataTable nesnelerini kullanan bir uygulamayı .Net 3.5' e taşıyarak basit LINQ sorgularını nasıl ele alabileceğimizi incelemeye çalışacağız. (LINQ sorgularının işlevselliğinin ön plana çıktığı vakalarda, var olan .Net uygulamaları .Net 3.5 versiyonuna terfi edilmek durumdan kalabilir.) Bu amaçla ilk olarak .Net Framework 2.0 ile geliştirilmiş ve test amacıyla aşağıdaki kodlara sahip bir Console uygulamamız olduğunu düşünelim.

using System;
using System.Data;
using System.Data.SqlClient;

namespace Net20DataTable
{
    class Program
    {
        static void Main(string[] args)
        {
            DataTable tbl = null;
   
            using (SqlConnection conn = new SqlConnection("data source=.;database=AdventureWorks;integrated security=SSPI"))
            {
                SqlDataAdapter adapter = new SqlDataAdapter("Select ProductId,Name,ListPrice,Class,SellStartDate,ProductSubCategoryId From Production.Product", conn);
                tbl = new DataTable("Products");
                adapter.Fill(tbl);
            }
        }
    }
}

Kod, AdventureWorks isimli SQL Server 2005 veritabanına bağlanmakta ve Production şemasındaki Product tablosundan bir kaç alanı çekmektedir. Çekilen veri kümesi işlenilmek üzere bir DataTable nesnesi içerisinde toplanmaktadır. Çok doğal olarak uygulama .Net Framework 2.0 tabanlı olduğundan, LINQ ifadelerinin DataTable üzerinden uygulanması(veya başka bağlantısız katman nesneleri üzerinden) mümkün değildir. Eğer elimizde Visual Studio 2008 var ise yapılması gerekenler çok basittir. Öncelikli olarak proje özelliklerinden(Properties) Application sekmesine geçilmeli ve Target Framework seçeneği .Net Framework 3.5 olarak değiştirilmelidir.

Bu işlemin ardından uygulamanın bir kere daha derlenmesinde yarar vardır. (Söz konusu adımların ardından System.Core.dll assembly' ının projeye hemen referans edildiğide görülebilir.) Artık LINQ sorgularının yazılmasına başlanabilir. DataTable için bu sorguların uygulanabilmesi için AsEnumerable metodunun erişilebilir olması gerekmektedir. Ancak bu genişletme metoduna şu anda erişilemediği görülmektedir. Bunun sebebi System.Data.DataSetExtensions.dll assembly' ının projeye referans edilmemiş olmasıdır. Dolayısıyla öncelikle bu assembly' ın referans edilmesi gerekmektedir.

Artık uygulamada yer alan DataTable üzerinde LINQ sorguları çalıştırılabilir. İşte bir örnek;

var altKategorisi4OlanUrunler = from row in tbl.AsEnumerable()
                                                    where row["ProductSubCategoryId"].ToString() == "4"
                                                        select new
                                                                    {
                                                                        Id = Convert.ToInt16(row["ProductId"]),
                                                                        Ad = row["Name"].ToString(),
                                                                        Fiyat = Convert.ToDouble(row["ListPrice"])
                                                                    };

foreach (var urun in altKategorisi4OlanUrunler)
    Console.WriteLine(urun.ToString());

Bu kod parçasında görülen LINQ sorgusuna göre DataTable içerisinden ProductSubCategoryId alanının değeri 4 olanların ProductId,Name,ListPrice kolonlarının verilerinden oluşan yeni bir isimsiz tip(Anonymous Type) topluluğu elde edilmektedir. Kodun ekran çıktısı aşağıdakine benzer olacaktır.

Görüldüğü gibi LINQ sorguları .Net Framework içerisinde pek çok farklı alanda uygulanabilmektedir. Reflection, IO, Windows Forms Controls, Application Log, DataTable, eski bir uygulamadan gelen ArrayList bu yazıda ele alınan basit bir kaç alandır. LINQ sorguları Office ürünlerinde dahi kullanılabilmektedir. Söz gelimi Outlook içerisindeki kontaklar LINQ sorguları ile filtrelenebilir. Örnekleri arttırmak ve yaymak mümkündür. Ancak unutulmaması gereken noktalardan biriside bu işlemlerin yapılması için LINQ sorgularının olmasının zorunlu olmadığıdır. Öyleki LINQ sorgularıda özünde, .Net Framework 3.5 ile gelen genişletme metodlarını(Extension Methods) yoğun bir şekilde ele almaktadır. Bir başka deyişle LINQ olmadanda metodlar yardımıyla bu istekler karşılanabilir. Diğer taraftan LINQ sorgularının getirdiği dil esnekliği, kullanım kolaylığı, anlaşılabilirlik göz ardı edilmemelidir. Her geliştirici kullandığı programlama dili yardımıyla nesneler üzerinden SQL benzeri sorgu ifadeleri yazabilmek ister. LINQ bu imkanı sağlayarak önemli bir açığı kapatmaktadır. Böylece geldik bir makalemizin daha sonuna. Bir sonraki makalemizde görüşünceye dek hepinize mutlu günler dilerim.

LINQveReflectionArastirma.rar (97,49 kb)

Tags:   ,
Categories:   C# 3.0 | LINQ
Actions:   E-mail | del.icio.us | Permalink | Yorumlar (0) | Comment RSSRSS comment feed
Bookmark and Share

C# 3.0 - Derinlemesine Lambda İfadeleri

Pazartesi, 31 Mart 2008 21:28 by bsenyurt

C# programlama dilinin 3ncü versiyonu ile birlikte gelen önemli yeniliklerden biriside lambda(=>) operatörüdür. Bu operatörün kullanıldığı ifadeler yardımıyla temsilci(delegate) oluşturulması, kod bloğunun yazılması, sonuçların alınması ve tip tahmini(Type Inference) gibi işlemlerin tek seferde gerçekleştirilmesi mümkündür. Bu sebepten dolayı LINQ(Language INtegrated Query) sorgularında yer alan genişletme metodlarında(Extension Methods) büyük öneme sahiptir. Ne varki lambda operatörünü kavramak için ona olan ihtiyacın nereden doğdunu bilmek ve nasıl bu operatöre ulaşıldığını anlamak gerekmektedir. En iyi başlangıç noktası elbetteki C# dilinin ilk versiyonudur. Bu yazımızda lambda operatörünün getirdiği avantajları görmeye çalışırken derinlemesinede inceliyor olacağız.

Herşeyden önce C# 1.0 versiyonunda kullanıcı tanımlı bir tipe ait koleksiyonlar üzerinde bazı sorgulamalar yapmak istediğimizi düşünelim. C# 1.0 versiyonunda generic mimari kavramı yoktur. Bu sebepten generic olarak türden bağımsız ve .Net Framework içerisinde önceden tanımlanmış olan koleksiyonlar bulunmamaktadır. Bunun yerine elemanları her zaman object türünden olan ArrayList, Stack, Queue gibi Collection bazlı koleksiyonlar ile Hashtable ve SortedList gibi Dictionary bazlı koleksiyonlar mevcuttur. Eğer sadece bizim istediğimiz tip ile çalışacak kuvvetle türlendirilmiş bir koleksiyon(Strongly Typed Collection) kullanmak istersek CollectionBase veya DictionaryBase abstract sınıflarından türetme yolunu tercih edebiliriz. Böylece sadece istenilen tipler ile çalışacak bir koleksiyonumuz olur.(Lakin bu koleksiyon tip güvenli-type safety olmasına rağmen gereksiz boxing ve unboxing işlemlerini engellemez.) Şu an için asıl amaç bu koleksiyon içerisinde yer alan tipler üzerinde farklı sorgular çalıştırabilmektir. Söz gelimi bir personelin bilgirini tanımlayan sınıfa ait bir koleksiyon içerisinden, çalışanın maaşına, adına, giriş tarihine göre sorgular yaparak alt koleksiyonların çekilmesini sağlayacak fonksiyonelliklerin olması istenebilir. Hatta bu metodarın sayısının arttırılmasınada olanak verecek şekilde esnek bir yapının geliştirilmesi istenebilir. Dolayısıyla alt koleksiyonları elde edebilmek için geliştirilen ortak bir metodun kullanacağı koşulsal fonksiyonelliklerin işaret edilebilmesi son derece yararlı olur. İşte bu noktada temsilciler(Delegates) devreye girmektedir. Bu cümleler ile tam olarak neye sebebiyet verdiğimizi görmek üzere C# 1.0 dilinin yeteneklerinin kullanıldığı aşağıdaki program kodu göz önüne alınabilir.

Kod içeriği;

using System;
using System.Collections;

namespace DotNet1Deyken
{
    enum Departman
    {
        BilgiIslem
        ,Yazilim
        ,Muhasebe
        ,InsanKaynaklari
        ,GenelMudurluk
    }

    class Personel
    {
        int _id;
        string _ad;
        string _soyad;
        Departman _bolumu;
        double _maas;
        DateTime _girisTarihi;

        public DateTime GirisTarihi
        {
            get { return _girisTarihi; }
            set { _girisTarihi = value; }
        }

        public string Soyad
        {
            get { return _soyad; }
            set { _soyad = value; }
        }

        public double Maas
        {
            get { return _maas; }
            set { _maas = value; }
        }

        internal Departman Bolumu
        {
            get { return _bolumu; }
            set { _bolumu = value; }
        }

        public string Ad
        {
            get { return _ad; }
            set { _ad = value; }
        }

        public int Id
        {
            get { return _id; }
            set { _id = value; }
        }

        public Personel(int id, string ad, string soyad, Departman bolumu, double maas,DateTime girisTarihi)
        {
            Id = id;
            Ad = ad;
            Soyad = soyad;
            Bolumu = bolumu;
            Maas = maas;
            GirisTarihi = girisTarihi;
        }
        public override string ToString()
        {   
            return String.Format("{0} {1} {2} {3} {4} {5}", Id.ToString(), Ad, Soyad.ToUpper(), Bolumu.ToString(), Maas.ToString("C2"), GirisTarihi.ToShortDateString());
        }
    }

    class PersonelList
        : CollectionBase
    {
        public void Ekle(Personel prs)
        {
            List.Add(prs);
        }
        public void Cikart(Personel prs)
        {
            List.Remove(prs);
        }
    }

    delegate bool KontrolHandler(Personel p);

    class Program
    {
        static bool DepartmaniIKmi(Personel p)
        {
            return p.Bolumu == Departman.GenelMudurluk;
        }
        static bool AdininBasHarfiBmi(Personel prs)
        {
            return prs.Ad[0] == 'B';
        }
        static bool Maas1000Uzerindemi(Personel prs)
        {
            return prs.Maas > 1000;
        }

        static PersonelList Bul(PersonelList liste,KontrolHandler handler)
        {
            PersonelList sonucListesi = new PersonelList();
            foreach (Personel prs in liste)
            {
                if (handler(prs))
                    sonucListesi.Ekle(prs);
            }
            return sonucListesi;
        }

        static void Listele(PersonelList liste)
        {
            foreach (Personel prs in liste)
                Console.WriteLine(prs.ToString());
            Console.WriteLine("");
        }

        static void Main(string[] args)
        {
            PersonelList calisanlar = new PersonelList();
   
            calisanlar.Ekle(new Personel(1000,"Mayk","Hemır", Departman.BilgiIslem,1050,new DateTime(1979,10,1)));
            calisanlar.Ekle(new Personel(1001,"Büyük","Başkan", Departman.GenelMudurluk,53000,new DateTime(1989,2,3)));
            calisanlar.Ekle(new Personel(1002,"EmSi","Hemmır", Departman.GenelMudurluk,13500,new DateTime(1990,2,4)));
            calisanlar.Ekle(new Personel(1003,"Tombul","Raydır", Departman.InsanKaynaklari,2250,new DateTime(1994,8,5)));
            calisanlar.Ekle(new Personel(1008,"Şirine","Şirin", Departman.BilgiIslem,900,new DateTime(1991,3,6)));
            calisanlar.Ekle(new Personel(1006,"Burak","Selim", Departman.InsanKaynaklari,2250,new DateTime(1976,7,3)));
            calisanlar.Ekle(new Personel(1004,"Osvaldo","Nartayyo", Departman.Muhasebe,3500,new DateTime(1975,6,3)));
            calisanlar.Ekle(new Personel(1005,"Higuin","Kim", Departman.Yazilim,1250,new DateTime(1974,4,2)));
            calisanlar.Ekle(new Personel(1007,"Karim","Cabbar", Departman.Yazilim,750,new DateTime(1975,2,7)));
            calisanlar.Ekle(new Personel(1011, "Billl", "Geytis", Departman.Yazilim, 650, new DateTime(1976, 3, 8)));

            // Departmanı Insan Kaynakları olanların bulunması
            PersonelList sonuclar1=Bul(calisanlar, new KontrolHandler(DepartmaniIKmi));
       
            // İsminin baş harfi B olanların bulunması
            PersonelList sonuclar2 = Bul(calisanlar, new KontrolHandler(AdininBasHarfiBmi));
       
            // Maaşı 1000 YTL üzerinde olanların bulunması
            PersonelList sonuclar3 = Bul(calisanlar, new KontrolHandler(Maas1000Uzerindemi));

            Listele(sonuclar1);
            Listele(sonuclar2);
            Listele(sonuclar3);
        }
    }
}

Öncelikli olarak bu uzun Console uygulaması kodlarında neler olduğuna bir bakalım. Personel isimli sınıf bir çalışanın Id' sini, adını, soyadını, işe giriş tarihini, maaşını, departmanını ve maaşını tutacak şekilde tanımlanmıştır. Bu sınıfa ait örneklerin içeriklerinin kolay bir şekilde string olarak alınabilmesi içinde ToString metodu Personel tipi içerisinde ezilmiştir(override). Personelin bölümü, Departman isimli bir enum sabiti ile belirtilmektedir. PersonelList isimli sınıf CollectionBase abstract sınıfından türetilmektedir. Bu nedenle Collection tabanlı bir koleksiyondur. Konunun kolay anlaşılabilmesi için sadece Ekle ve Cikart isimli iki fonksiyonelliğe sahiptir. Bu metodlar sadece Personel tipinden parametreler almaktadır. Buda zaten PersonelList isimli koleksiyonun tip güvenli(Type Safety) olmasını sağlamaktadır. Dikkatimiz çeken tiplerden biriside KontrolHandler isimli temsilcidir(delegate).

Not: Temsilcileri(delegates) metodları işaret edebilecek şekilde kullanılabilen .Net tipidir. Bir temsilci işaret edebileceği metodun parametrik yapısı ile dönüş tipinide belirtmektedir.

Söz konusu temsilci, Personel tipinden bir parametre alan ve geriye bool değer döndüren metodları işaret edecek şekilde tanımlanmıştır. Bu temsilcinin tek bir tasarım amacı vardır. Buna göre, bir Personel nesne örneğinin herhangibir şartı sağlayıp sağlamadığına dair true veya false değer döndürecek bir metodun işaret edilmesini sağlamaktadır. Peki neden böyle bir temsilciye ihtiyacımız vardır? Bu sorunun cevabını Bul isimli fonksiyon vermektedir.

static PersonelList Bul(PersonelList liste,KontrolHandler handler)
{
    PersonelList sonucListesi = new PersonelList();
    foreach (Personel prs in liste)
    {
        if (handler(prs))
            sonucListesi.Ekle(prs);
    }
    return sonucListesi;
}

Dikkat edilecek olursa Bul metodu geriye PersonelList tipinden bir nesne örneği döndürmektedir. Bu nesne örneği metod içerisinde oluşturulmaktadır. Oluşturulma işlemi sırasında ise belirli bir şarta bakılmaktadır. Nitekim bu şartın ne olduğu belli değildir. Ancak şartın sonucunun alınmasını sağlayan metodu işaret edebilecek KontrolHandler tipinden bir temsilci, fonksiyona parametre olarak gelmektedir. Temsilci nesne örneği çalışma zamanında(runtime) ilgili fonksiyonu işaret edeceğinden, if ifadesi içerisindeki çağrı aslında o andaki Personel nesne örneği için koşul metoduna doğru yapılan bir yürütmeden başka bir şey değildir.

Artık tek yapılması gereken şartları sağlayacak metodların yazılması ve sonrasında ise Bul fonksiyonelliğinin kullanılarak ilgili sonuç kümesinin alınmasıdır. Örneğin IK departmanında çalışan personelin bulunabilmesi için DepartmanIKmi isimli bir metod geliştirilmiştir.

static bool DepartmaniIKmi(Personel p)
{
    return p.Bolumu == Departman.GenelMudurluk;
}

Bu metod basitçe gelen Personel nesne örneğinin Bolumu özelliğine bakmakta ve geriye true yada false değerini döndürmektedir. Başka bir örnek olarak maaşı 1000 YTL üzerinde olanların elde edilmesi istenebilir. Bunun içinde Maas1000Uzerindemi isimli bir metod geliştirilmiştir.

static bool Maas1000Uzerindemi(Personel prs)
{
    return prs.Maas > 1000;

Tahmin edileceği üzere bu fonksiyonda, KontrolHandler temsilcisinin belirttiği yapıya uygun bir şekilde tasarlanmıştır. Bu yaklaşımlar göz önüne alındığında koleksiyon içerisinde istenildiği gibi filtreleme yapılabileceği görülmektedir. Tek şart temsilciye uygun tipte bir karşılaştırma fonksiyonelliğinin var olmasını sağlamaktır. Uygulamanın çalışma zamanındaki çıktısı aşağıdaki gibi olacaktır.

Görüldüğü gibi maaşı 1000 YTL üzerinde olanlar, IK departmanında çalışanlar ve isminin baş harfi B olanlar kolay bir şekilde elde edilmektedir.

Bu yaklaşım her ne kadar kolay ve anlaşılır olsada bazı sıkıntılar olduğu ortadadır. Herşeyden önce Bul fonksiyonunun parametresi olan temsilci tipinin işaret edeceği metod bloklarının ayrı ayrı yazılıyor olma şartı vardır. Diğer taraftan söz konusu mimari şuanda sadece PersonelList isimli koleksiyona uygulanabilecek şekilde tasarlanmıştır. Hatta KontrolHandler isimli temsilci dahi sadece Personel tipleri ile çalışabilecek şekilde ele alınabilmektedir. Oysaki bu yapının herhangibir koleksiyon tipi içerisinde ele alınabilmesi sağlanabilmelidir. Bu, ilgili yapının sadece uygulama bazlı değil Framework bazlı olacak şekilde genişletilebilmesini sağlayacaktır ki bu oldukça önemlidir. Elbette bu iş sanıldığı kadar kolay değildir. Nitekim türden bağımsız olacak şekilde fonksiyonel yapıların olması şarttır. Peki öyleyse olaya C# 2.0 açısından bakmaya çalışalım. Bu sefer elimizde isimsiz metodlar(Anonymous Methods) ve generic mimari gibi  oldukça güçlü kozlar yer almaktadır. Dolayısıyla yukarıdaki örnek mimari modeli C# 2.0 içerisinde aşağıdaki şekilde ele alınabilir.

using System;
using System.Collections.Generic;

namespace DotNet2Deyken
{
    enum Departman
    {
        BilgiIslem
        ,Yazilim
        ,Muhasebe
        ,InsanKaynaklari
        ,GenelMudurluk
    }

    class Personel
    {
        int _id;
        string _ad;
        string _soyad;
        Departman _bolumu;
        double _maas;
        DateTime _girisTarihi;

        public DateTime GirisTarihi
        {
            get { return _girisTarihi; }
            set { _girisTarihi = value; }
        }

        public string Soyad
        {
            get { return _soyad; }
            set { _soyad = value; }
        }

        public double Maas
        {
            get { return _maas; }
            set { _maas = value; }
        }

        internal Departman Bolumu
        {
            get { return _bolumu; }
            set { _bolumu = value; }
        }

        public string Ad
        {
            get { return _ad; }
            set { _ad = value; }
        }

        public int Id
        {
            get { return _id; }
            set { _id = value; }
        }

        public Personel(int id, string ad, string soyad, Departman bolumu, double maas, DateTime girisTarihi)
        {
            Id = id;
            Ad = ad;
            Soyad = soyad;
            Bolumu = bolumu;
            Maas = maas;
            GirisTarihi = girisTarihi;
        }
        public override string ToString()
        {
            return String.Format("{0} {1} {2} {3} {4} {5}", Id.ToString(), Ad, Soyad.ToUpper(), Bolumu.ToString(), Maas.ToString("C2"), GirisTarihi.ToShortDateString());
        }
    }

    // Koşul kontrolünü yapabilecek metodları içeren tür bağımsız temsilci(Generic Delegate) tanımı
    delegate bool KontrolHandler<T>(T parametre);

    class Program
    {
        // generic tipten oluşan koleksiyon üzerinden alt küme çekme işlemini üstlenen metod
        static List<T> Bul<T>(List<T> liste,KontrolHandler<T> handler)
        {
            List<T> sonuclar = new List<T>();
            foreach (T eleman in liste)
                if (handler(eleman)) // Generic temsilcinin işaret edeceği karşılaştırma metodu çağırılır.
                    sonuclar.Add(eleman);
            return sonuclar;
        }   

        // Generic Listeleme fonksiyonu
        static void Listele<T>(List<T> liste)
        {
            foreach (T t in liste)
                Console.WriteLine(t.ToString());
            Console.WriteLine("");
        }

        static void Main(string[] args)
        {   
            List<Personel> calisanlar = new List<Personel>();
   
            calisanlar.Add(new Personel(1000, "Mayk", "Hemır", Departman.BilgiIslem, 1050, new DateTime(1979, 10, 1)));
            calisanlar.Add(new Personel(1001, "Büyük", "Başkan", Departman.GenelMudurluk, 53000, new DateTime(1989, 2, 3)));
            calisanlar.Add(new Personel(1002, "EmSi", "Hemmır", Departman.GenelMudurluk, 13500, new DateTime(1990, 2, 4)));
            calisanlar.Add(new Personel(1003, "Tombul", "Raydır", Departman.InsanKaynaklari, 2250, new DateTime(1994, 8, 5)));
            calisanlar.Add(new Personel(1008, "Şirine", "Şirin", Departman.BilgiIslem, 900, new DateTime(1991, 3, 6)));
            calisanlar.Add(new Personel(1006, "Burak", "Selim", Departman.InsanKaynaklari, 2250, new DateTime(1976, 7, 3)));
            calisanlar.Add(new Personel(1004, "Osvaldo", "Nartayyo", Departman.Muhasebe, 3500, new DateTime(1975, 6, 3)));
            calisanlar.Add(new Personel(1005, "Higuin", "Kim", Departman.Yazilim, 1250, new DateTime(1974, 4, 2)));
            calisanlar.Add(new Personel(1007, "Karim", "Cabbar", Departman.Yazilim, 750, new DateTime(1975, 2, 7)));
            calisanlar.Add(new Personel(1011, "Billl", "Geytis", Departman.Yazilim, 650, new DateTime(1976, 3, 8)));

            // Anonymous Method yardımıyla arama işlemleri yapılır
            // Bul metodunun ikinci parametrelerinin nasıl verildiğine dikkat edelim

            // Insan Kaynakları departmanında çalışanların bulunması
            List<Personel> IKCalisanlari=Bul<Personel>(calisanlar,delegate(Personel p)
                                                                                    {
                                                                                        return p.Bolumu == Departman.Yazilim;
                                                                                    }
                                                                                );

            // Şubat ayında işe girenlerin bulunması
            List<Personel> SubatAyindaBaslayanlar = Bul<Personel>(calisanlar, delegate(Personel p)
                                                                                    {
                                                                                        return p.GirisTarihi.Month == 2;
                                                                                    }
                                                                                );

            //Departmanı Yazilim olanlardan Maaşı 1000 YTL üzerinde olanların bulunması
            List<Personel> MaasiVeDepartmaninaGore = Bul<Personel>(calisanlar, delegate(Personel p)
                                                                                    {
                                                                                        return (p.Maas >= 1000 && p.Bolumu == Departman.Yazilim);
                                                                                    }
                                                                                );
            Listele<Personel>(IKCalisanlari);
            Listele<Personel>(SubatAyindaBaslayanlar);
            Listele<Personel>(MaasiVeDepartmaninaGore);
        }
    }
}

Bu uzun kod parçasında bir önceki versiyona göre en büyük farklılıklar generic koleksiyon ile generic ve isimsiz metod(Anonymous Method) kullanımlarıdır. Dikkat edilecek olursa herhangibir tipteki List koleksiyonu üzerinde arama işlemi yapılabilmesini sağlayacak şekilde generic bir Bul metodu yer almaktadır. Dahada önemlisi, koleksiyon içerisindeki elemanların kıyaslama işlemlerinin yapılacağı metodları işaret edebilecek olan temsilcide generic olarak tanımlanmıştır.  Bu sayede T tipindeki bir List koleksiyonu içerisinde Bul metodunun kullanılabilmesi ve o tip için bir koşullandırma yapılabilmesi sağlanmaktadır. Fakat bütün bunlara rağmen en çok dikkate değer kısımlardan biriside, isimsiz metodların kullanımıdır. Bu sebepten dolayı bir önceki örnekte olduğu gibi, ayrı ayrı karşılaştırma metodlarının yazılmasına gerek kalmamaktadır. Tam aksine Bul metodunun kullanıldığı yerlerde ikinci parametrelerde isimsiz metod kullanılarak koşul deyimlerinin aynı ifade içerisinde tanımlanabilmeside sağlanmıştır. Örneğin Şubat ayında işe giren personelin bulunabilme sürecini göz önüne alalım. Burada Bul metodu, calisanlar isimli generic koleksiyondaki Personel nesne örneklerini tek tek dolaşmalı, GirisTarihi özellikleri üzerinden Month değerlerinin 2 olup olmadığına bakmalı ve eğer öyleyse bunları yeni bir koleksiyonda birleştirerek geriye döndürmelidir. İsimsiz metodlar yardımıyla bu iş aşağıda görüldüğü gibi tek bir ifadede sağlanabilir.

List<Personel> SubatAyindaBaslayanlar = Bul<Personel>(calisanlar, delegate(Personel p)
                                                                                            {
                                                                                                return p.GirisTarihi.Month == 2;
                                                                                            }
                                                                                        );

Dikkat edilecek olursa delegate anahtar kelimesi burada KontrolHandler tipini işaret etmektedir. Dahası kod yazılırken generic mimarinin, Visual Studio IDE' si içerisinde aşağıdaki şekilde ele alındığı görülmektedir.

Görüldüğü gibi Bul metoduna generic parametre olarak Personel tipi verildiğinde, liste isimli List<T> koleksiyonu ve handler isimli KontrolHandler temsilcisi otomatik olarak bu tiple çalışacak hale gelmektedir. Buda Bul metodunun generic yapısından kaynaklanmaktadır.

Sonuç olarak program çıktısı aşağıdaki gibi olacaktır.

Elbette Bul fonksiyonu geliştirici tarafından yazılmış bir metoddur. Oysaki .Net Framework 2.0 özellikle List<T> koleksiyonları üzerinde bu tip filtreleme ve arama işlemlerinin gerçekleştirilmesi amacıyla hazır Predicate temsilcisini kullanan Find, FindAll, Exists, FindIndex, FindLast, FindLastIndex, RemoveAll gibi metodlar içermektedir. (Burada hazır bir temsilcinin olması geliştiricinin uygulamadan bağımsız olacak şekilde, Framework' ün kullanıldığı her yerde söz konusu koşullandırma metodlarını işaret ederek, başka hazır CLR tipi metodlarına parametre olarak verebileceği anlamına da gelmektedir.) Dolayısıyla yukarıda geliştirilen örnek, .Net Framework 2.0' ın tipleri sayesinde aşağıdaki hale getirilebilir.

class Program
{
    static void Listele<T>(List<T> liste)
    {
        foreach (T t in liste)
            Console.WriteLine(t.ToString());
        Console.WriteLine("");
    }

    static void Main(string[] args)
    {
        List<Personel> calisanlar = new List<Personel>();
       
        #region Test Verileri

        // Test verilerinin girildiği kodlar

        #endregion
       
        List<Personel> BHarfliler =
                                calisanlar.FindAll(delegate(Personel p)
                                                            {
                                                                return p.Ad[0] == 'B';
                                                            }
                                                        );
        List<Personel> SubattaBaslayanlar=
                                calisanlar.FindAll(delegate(Personel p)
                                                            {
                                                                return p.GirisTarihi.Month == 2;
                                                            }
                                                        );
        List<Personel> GenelMudurlukCalisanlari =
                                calisanlar.FindAll(delegate(Personel p)
                                                            {
                                                                return p.Bolumu == Departman.GenelMudurluk;
                                                            }
                                                        );
   
        Listele<Personel>(BHarfliler);
        Listele<Personel>(SubattaBaslayanlar);
        Listele<Personel>(GenelMudurlukCalisanlari);
    }
}

Bu kez delegate anahtar kelimesi FindAll metodunun istediği Predicate<T> temsilcisini işaret etmektedir. Kod yazımı sırasında intellisense ile bu açık bir şekilde görülmektedir.

Mimaride halen daha eksiklikler vardır. Özellikle veri tabanı uygulamalarında yer alan sorgulama tekniklerinin, programatik tarafta ifade edilme zorlukları bilinmektedir. Çok basit olarak düşünüldüğünde, bir veritabanı tablosunun program tarafında Entity olarak sınıf bazlı ifade edilmesi sonrasında geliştiricilerin beklentisi, veri sorgulama dili esnekliğinin nesnel olarakta sağlanabilmesidir. Bir başka deyişle bilinen select, where, group by, distinct, sum vb... sorgulama kelimelerinin, program tarafındaki nesnel varlıklar üzerinde de uygulanamabilmesi istenmektedir. İşte bu LINQ mimarisinin geliştirilmesinin en büyük nedenlerinden de birisidir. Peki elde bulunan imkanlar ile bu nasıl sağlanabilir? Yoksa dile yeni bir takım kolaylaştırıcı özelliklerin entegre edilmesimi gerekmektedir?

Herşeyden önce son örnekte yer alan FindAll metodu ile, bir koleksiyon üzerinde filtreleme yapılabildiği görülmektedir. Bu bir anlamda Where ve Select gibi ifadelerin bir karşılığı olarak göz önüne alınabilir. Ancak bu yeterli değildir. Yeni tipler geliştirmeden, var olan .Net Framework tiplerine FindAll metoduna benzer fonksiyonel yenilikleri ilave edebilmek gerekmektedir. İşte bu noktada Extension metodlar devreye girerek özellikle IEnumerable<T> uyarlamalı tiplerin genişletme metodları ile LINQ mimarisine destek verebilmesi sağlanmaktadır. İşin içerisinde yine temsilci(delegate) tipleri rol almaktadır. LINQ mimarisine destek verebilmek için pek çok temsilci tipi geliştirilmiş ve Framework içerisine dahil edilmiştir. Bu kadar ilerlemeden önce, yazıya konu olan örneğin C# 3.0 içerisindeki geliştirilme şekline bakmakta yarar vardır. Nitekim ilk hedef Lambda ifadelerinin rolünü kavramaktır. (Bu seferki örnek Visual Studio 2008 üzerinde .Net Framework 3.5 seçilerek yapılmıştır.)

using System;
using System.Collections.Generic;
using System.Linq;

namespace DotNet3Nokta5Deyken
{
    enum Departman
    {
        BilgiIslem
        ,Yazilim
        ,Muhasebe
        ,InsanKaynaklari
        ,GenelMudurluk
    }
    class Personel // Bu sınıfta otomatik özellikler(Automatic Property) kullanılmıştır.
    {
        public int Id { get; set; }
        public string Ad { get; set; }
        public string Soyad { get; set; }
        public Departman Bolumu { get; set; }
        public double Maas { get; set; }
        public DateTime GirisTarihi { get; set; }

        public override string ToString()
        {
            return String.Format("{0} {1} {2} {3} {4} {5}", Id.ToString(), Ad, Soyad.ToUpper(), Bolumu.ToString(), Maas.ToString("C2"), GirisTarihi.ToShortDateString());
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            // Object Initializers' dan faydalanılmıştır.
            List<Personel> calisanlar = new List<Personel>()
            {
                new Personel(){Id=1000, Ad="Mayk", Soyad="Hemır", Bolumu=Departman.BilgiIslem, Maas=1050, GirisTarihi=new DateTime(1979, 10, 1)},
                new Personel(){Id=1001, Ad="Büyük", Soyad="Başkan",Bolumu= Departman.GenelMudurluk,Maas= 53000,GirisTarihi= new DateTime(1989, 2, 3)},
                new Personel(){Id=1002, Ad="EmSi", Soyad="Hemmır", Bolumu=Departman.GenelMudurluk, Maas=13500, GirisTarihi=new DateTime(1990, 2, 4)},
                new Personel(){Id=1003, Ad="Tombul", Soyad="Raydır", Bolumu=Departman.InsanKaynaklari, Maas=2250, GirisTarihi=new DateTime(1994, 8, 5)},
                new Personel(){Id=1008, Ad="Şirine", Soyad="Şirin",Bolumu= Departman.BilgiIslem, Maas=900, GirisTarihi=new DateTime(1991, 3, 6)},
                new Personel(){Id=1006, Ad="Burak", Soyad="Selim", Bolumu=Departman.InsanKaynaklari,Maas= 2250, GirisTarihi=new DateTime(1976, 7, 3)},
                new Personel(){Id=1004, Ad="Osvaldo", Soyad="Nartayyo", Bolumu=Departman.Muhasebe, Maas=3500,GirisTarihi= new DateTime(1975, 6, 3)},
                new Personel(){Id=1005, Ad="Higuin", Soyad="Kim",Bolumu= Departman.Yazilim, Maas=1250,GirisTarihi= new DateTime(1974, 4, 2)},
                new Personel(){Id=1007, Ad="Karim", Soyad="Cabbar", Bolumu=Departman.Yazilim, Maas=750, GirisTarihi=new DateTime(1975, 2, 7)},
                new Personel(){Id=1011, Ad="Billl", Soyad="Geytis", Bolumu=Departman.Yazilim, Maas=650,GirisTarihi= new DateTime(1976, 3, 8)}
            };

            // B ile başlayanlar
            var AdiBIleBaslayanlar = calisanlar.FindAll((Personel p) => (p.Ad[0] == 'B'));

            // Departmanı Yazilim olanlar (Burada type inference söz konusu)
            var YazilimDepartmaniCalisanlari = calisanlar.FindAll(p => p.Bolumu == Departman.Yazilim);

            //Giris yılı 1976 öncesi olanlar çekilirken başka bir metod çağırılıyor.
            var GirisYili1976OncesiOlanlar = calisanlar.FindAll(
                                                                            p =>{
                                                                                if (p.GirisTarihi.Year < 1976)
                                                                                {
                                                                                    PrimArttir(p);
                                                                                    return true;
                                                                                }
                                                                                else
                                                                                    return false;
                                                                            }
                                                                    );

            Listele<Personel>(AdiBIleBaslayanlar);       
            Listele<Personel>(YazilimDepartmaniCalisanlari);
            Listele<Personel>(GirisYili1976OncesiOlanlar);
        }

        private static void PrimArttir(Personel p)
        {
            Console.WriteLine("\t"+p.Ad+" "+p.Soyad.ToUpper()+" için prim arttırım talebi");
        }
   
        static void Listele<T>(IEnumerable<T> liste)
        {
            foreach (T t in liste)
                Console.WriteLine(t.ToString());
            Console.WriteLine("");
        }
    }
}

Örnekte C# 3.0 ile birlikte gelen pek çok yenilik kullanılmaya çalışılmıştır. var anahtar kelimesi, koleksiyon ve Personel nesnelerininin başlatılması(object initialization), otomatik özellikler(automatic properties) gibi. Ancak yazımızda odaklanacağımız nokta => operatörü ve içerisinde yer aldığı ifadelerdir. Dikkat edilecek olursa FindAll metodlarının içerisinde kullanılan parametrelerde => operatörleri yer almaktadır. İlk metod çağrısı aşağıdaki gibidir.

var AdiBIleBaslayanlar = calisanlar.FindAll((Personel p) => (p.Ad[0] == 'B'));

Burada => operatörünün sol tarafında Personel tipinden bir değişken tanmı yer almaktadır. Operatörün sağ tarafında ise yine parantezler içerisinde p değişkeninin Ad özelliğinin ilk karakterine bakılmaktadır. Daha önceki örneklerden hatırlanacağı gibi FindAll metodu Predicate temsilcisini parametre olarak almaktadır. Bu temsilci geriye bool değer döndüren ve generic tipte parametre alan metodları taşıyabilemektedir. Bu sebepten Lambda operatörünün sağ tarafında yer alan kod parçasının bool tipinden bir değer döndürüyor olması şarttır. Predicate temsilcisinin işaret edeceği metodun alacağı parametre ise operatörün sol tarafında belirtilmektedir. Peki burada Lambda operatörü neyi sağlamaktadır? Nitekim aynı amaç için isimsiz metod(anonymous method) kullanımıda mümkündür. Hatta isimsiz metod kullanmadanda yapılabildiği görülmektedir. Ne farki fonksiyonel programlama ortamlarına bakıldığında bu tip ifadelerin yaygın bir şekilde ele alındığı görülmüştür. Bununla beraber => operatörü burada, temsilcinin örneklenmesi, işaret edeceği metoda parametre aktarılması, uygun tipte sonuç üreten bir kod bloğunun yazılması operasyonlarının tek bir ifade içerisinde gerçekleştirilmesini sağlamaktadır.

İkinci kullanım şekli ilkinden biraz daha farklıdır.

var YazilimDepartmaniCalisanlari = calisanlar.FindAll(p => p.Bolumu == Departman.Yazilim);

Bu sefer dikkat çekici nokta => operatörünün sol ve sağ tarafındaki deyimlerde parantez kullanılmayışı değildir. Dikkat edilmesi gereken nokta operatörün sol tarafında sadece p yazılmasıdır. Oysaki bir önceki kullanım şeklinde temsilcinin işaret ettiği metoda aktarılacak olan parametrenin tipi açık bir şekilde belirtilmiştir. Burada tip tahmini(type inference) kavramı devreye girmektedir. Öyleki FindAll metodunun, List koleksiyonunun generic yapısına göre kullanacağı tipin Personel olma olasılığı muhtemeldir. Bu son derece doğaldır nitekim calisanlar değişkeni List<Personel> tipinden bir koleksiyonu taşımaktadır. Buna göre compiler, p değişkeninin Personel tipinden olacağını tahmin eder. Bu tahminin ne kadar tutarlı olduğu Visual Studio IDE' si içerisinde intellisense özelliği ile açık bir şekilde görülebilmektedir.

Görüldüğü gibi lambda operatörünün sağ tarafında p değişkeni kullanılmak istendikten sonra, tahmin edilen tipin üyeleri ekrana gelmektedir.

Üçüncü kullanım şekli ise aşağıdaki gibidir.

var GirisYili1976OncesiOlanlar = calisanlar.FindAll(
                                                                            p =>{
                                                                                if (p.GirisTarihi.Year < 1976)
                                                                                {
                                                                                    PrimArttir(p);
                                                                                    return true;
                                                                                }
                                                                                else
                                                                                    return false;
                                                                            }
                                                                    );

Burada ise tek fark lambda operatörünün sağ tarafında yer alan deyimlerde normal kod bloklarınında geliştirilebiliyor olmasıdır. Bir başka deyişle Predicate temsilcisinin istediği şekilde bool değer döndürmek dışında, metod çağrısı gibi farklı deyimlerde yapılabilmektedir.

Geliştirilen son örnek çalıştırıldığında aşağıdaki ekran görüntüsünde yer alan sonuçların alındığı görülmektedir.

Lambda operatörleri özellikle LINQ içerisinde yer alan genişletme metodlarında sıklıkla kullanılmaktadır. Bilindiği üzere LINQ sorgularının desteklenmesi için Enumerable(System.Core.dll assembly' ı içerisinde yer alan System.Linq isim alanında yer almaktadır) isimli static sınıf içerisine çok sayıda genişletme metodu(Extension Methods) dahil edilmiştir. Bu sınıfın en büyük özelliklerinden biriside içerisinde yer alan metodlarının IEnumerable<T> türevli tipleri genişletmesidir. Diğer taraftan söz konusu sınıf içerisinde çoğunlukla Func isimli generic temsilci kullanılmaktadır. Bu temsilcisinin farklı versiyonları aşağıdaki gibidir.

delegate TResult Func<TResult>(T arg)
delegate TResult Func<T, TResult>(T arg)
delegate TResult Func<T1,T2, TResult>(T1 arg1, T2 arg2)
delegate TResult Func<T1,T2,T3 TResult>(T1 arg1, T2 arg2, T3 arg3)
delegate TResult Func<T1,T2,T3,T4, TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4)

Func bir temsilci olduğu için, kullanılacağı her yerde lambda operatörleri ele alınabilir. Buna çok basit olarak aşağıdaki kod parçasını örnek gösterebiliriz.

double sonuc = calisanlar
                            .Where<Personel>(p => p.Bolumu == Departman.Yazilim)
                                .Sum<Personel>(p => p.Maas);
Console.WriteLine(sonuc.ToString("C2"));

int sonuc2 = calisanlar.Aggregate(0,(toplam,p) => p.Maas>2000?toplam+=1:toplam);
Console.WriteLine(sonuc2.ToString());

İlk kullanımda departmanı yazılım olan personelin maaşlarının toplamı bulunmuştur. İkinci kullanımda ise lambda operatörünün iki parametreyi birden temsilciye gönderdiği görülmektedir. Dikkat edilecek olursa operatörün sol tarafında toplam ve p isimli değişkenler tanımlanmaktadır.(Bu iki parametre bildirimi eğer parantezler içersinde yazılmassa derleme zamanı hatası alınır. Dolayısıyla lambda operatörünün sol tarafında birden fazla parametre olacaksa bunların parantezler içerisine alınması gerekmektedir.) Buradaki toplam değişkeni yine tahmin edilerek int tipinden belirlenmiştir. Bu tarz bir kullanım normaldir nitekim Func temsilcisinin bu şekilde iki parametre ile çalışan versiyonu mevcuttur. Buna göre Aggregate metodu, personelin maaşı 2000 YTL üzerinde olanlar var ise, toplam değişkeninin değerini 1 arttırarak geriye döndürmektedir. (Bu işlem için Sum metoduda kullanılabilir. Aggregate genişletme metodunun yazılmasının amacı Sum, Count, Max, Min, Avg gibi standart gruplama fonksiyonları dışındaki gereksinimlerin karşılanmasıdır.)

Artık Lambda operatörünün kullanımı hakkında fikir sahibi olduğumuzu sanıyorum. Şimdi diğer noktalara değinmeye çalışalım. Söz gelimi lambda operatörünün yer aldığı ifadeler IL(Intermediate Language) tarafında nasıl yorumlanmaktadır? Bu noktada lambda operatörünün, isimsiz metod kullanımı ile aynı IL çıktısını verdiğini söyleyebiliriz. Örnek olarak aşağıdaki kod parçasını göz önüne alalım.

using System;

namespace LambdaVeCIL
{
    delegate T IslemHandler<T>(T T1,T T2);

    class Program
    {
        static void Main(string[] args)
        {
            IslemHandler<double> hnd = (x, y) => x + y;

            IslemHandler<int> hnd2=
                delegate(int a,int b){
                    return a + b;
                };
        }
    }
}

Yukarıdaki kod parçasında yer alan IslemHandler isimli generic temsilci tipi, T türünden iki parametre alan ve yine T türünden sonuç üreten metodları işaret edebilecek şekilde tasarlanmıştır. hnd değişkeni oluşturulurken lambda ifadesinden, hnd2 oluşturulurkende isimsiz metoddan(anonymous method) yararlanılmıştır. Bu kodun IL çıktısına ildasm aracı ile bakıldığında ağaç yapısının aşağıdaki gibi olduğu görülür.(Ağaç yapısının kolay bir şekilde elde edilmesi için Dump TreeView seçeneğinden yararlanılmıştır.)

Program içerisinde b__0 ve b__1 adları ile tanımlanmış iki adet metod olduğu görülmektedir. Tahmin edileceği üzere bu iki üye, lambda ifadesi ve isimsiz metod kullanımı sonrası oluşturulmuş metodlardır. Bir başka deyişle lambda operatörü kullanıldığında aynen isimsiz metodlarda olduğu gibi IL tarafında iş yapan metod oluşturulmaktadır. Bu metodlar derleyici tarafından oluşturulan gizli metodlardır. Compiler tarafından oluşturuldukları için CompilerGenerated niteliği(Attribute) ile imzalanmışlardır. Eğer Main metodunun IL çıktısına bakılırsa aşağıdaki kod parçalarının üretildiği görülür.

.method private hidebysig static void Main(string[] args) cil managed
{
    .entrypoint
    // Code size 66 (0x42)
    .maxstack 3
    .locals init ([0] class LambdaVeCIL.IslemHandler`1<float64> hnd,[1] class LambdaVeCIL.IslemHandler`1<int32> hnd2)
    IL_0000: nop
    IL_0001: ldsfld class LambdaVeCIL.IslemHandler`1<float64> LambdaVeCIL.Program::'CS$<>9__CachedAnonymousMethodDelegate2'
    IL_0006: brtrue.s IL_001b
    IL_0008: ldnull
    IL_0009: ldftn float64 LambdaVeCIL.Program::'<Main>b__0'(float64,float64)
    IL_000f: newobj instance void class LambdaVeCIL.IslemHandler`1<float64>::.ctor(object, native int)
    IL_0014: stsfld class LambdaVeCIL.IslemHandler`1<float64> LambdaVeCIL.Program::'CS$<>9__CachedAnonymousMethodDelegate2'
    IL_0019: br.s IL_001b
    IL_001b: ldsfld class LambdaVeCIL.IslemHandler`1<float64> LambdaVeCIL.Program::'CS$<>9__CachedAnonymousMethodDelegate2'
    IL_0020: stloc.0
    IL_0021: ldsfld class LambdaVeCIL.IslemHandler`1<int32> LambdaVeCIL.Program::'CS$<>9__CachedAnonymousMethodDelegate3'
    IL_0026: brtrue.s IL_003b
    IL_0028: ldnull
    IL_0029: ldftn int32 LambdaVeCIL.Program::'<Main>b__1'(int32,int32)
    IL_002f: newobj instance void class LambdaVeCIL.IslemHandler`1<int32>::.ctor(object,native int)
    IL_0034: stsfld class LambdaVeCIL.IslemHandler`1<int32> LambdaVeCIL.Program::'CS$<>9__CachedAnonymousMethodDelegate3'
    IL_0039: br.s IL_003b
    IL_003b: ldsfld class LambdaVeCIL.IslemHandler`1<int32> LambdaVeCIL.Program::'CS$<>9__CachedAnonymousMethodDelegate3'
    IL_0040: stloc.1
    IL_0041: ret
} // end of method Program::Main

Her ne kadar IL(Intermediate Language) tarafı karışık görünsede dikkat edilmesi gereken noktalar IL_0001 - IL_0020 aralığındaki yapının IL_0020 - IL_0040 arasındaki ile aynı olmasıdır. Söz edilen ilk aralıkta lambda ifadesinin kullanıldığı satıra ait üretimler yer almaktadır. İkinci parçada ise isimsiz metod kullanımına ait üretimler bulunmaktadır. Yazımızın asıl amacı IL tarafındaki üretimleri kavramak değildir ancak sonuç itibariyle lambda ifadeleri, isimsiz metodlar ile aynı IL çıktılarının üretilmesini sağlamaktadır.

Son olarak lambda ifadeleri kullanılırken dikkat edilmesi gereken bazı durumları göz önüne alalım.

1 - Lambda ifadelerinde tanımlanan değişkenler diğer metodlar tarafından kullanılamazlar. Başka bir deyişle, değişkenlerin kapsamı lambda ifadesinin sınırlarıdır. Aşağıdaki ekran görüntüsünde bu durum ifade edilmektedir. Görüldüğü gibi ifade içerisinde tanımlanan d değişkenine kapsam dışından erişilememekte ve derleme zamanı hatası(Compile-Time Error) alınmaktadır.

2 - Elbette lambda ifadesi dışında tanımlanmış olan bir değişkene, ifade içerisinden erişilebilmektedir. Söz gelimi aşağıdaki ekran çıktısındanda görüleceği gibi, d değişkeni lambda ifadesi dışında 10 olarak tanımlanmış ve metod çağrısından sonra 11 olarak değiştirilmiştir.

3 - Lambda ifadelerinin sol tarafında yer alan parametrelerde ref ve out anahtar kelimeleri kullanılamaz.

Bu yazımızda lambda ifadelerinin genel kullanımı üzerinde durulmaya çalışmıştır. Lambda ifadelerinin getirdiği kolaylığı görmek amacıyla C# 1.0 tarafından C# 3.0 tarafına doğru ilerlenmeye çalışılmıştır. Lambda ifadeleri ile ilişkili bir diğer önemli konuda ifade ağaçlarıdır(Expression Trees). Bu konuyu ilerleyen yazılarımızda incelemeye çalışacağız. Böylece geldik bir makalemizin daha sonuna. Bir sonraki makalemizde görüşünceye dek hepinize mutlu günler dilerim.

LambdayiAnlamak.rar (119,00 kb)

Tags:  
Categories:   C# 3.0
Actions:   E-mail | del.icio.us | Permalink | Yorumlar (1) | Comment RSSRSS comment feed
Bookmark and Share

C# 3.0 - Derinlemesine Extension Method Kavramı

Cuma, 14 Mart 2008 20:23 by bsenyurt

Bilindiği üzere Language INtegrated Query(LINQ) mimarisinin uygulanışında C# 3.0(Visual Basic 9.0) ile birlikte gelen yenilikler oldukça önemli bir yere sahiptir. Bu yeniliklerin çoğu var olan .Net Framework 2.0 yapısını bozmadan genişletebilmek amacıyla tasarlanmıştır. Genişletme Metodları(Extension Methods) bu yeniliklerden sadece bir tanesidir.(Object Initializers, Anonymous Types, Partial Methods, var anahtar kelimesi, auto-implemented property, => operatörü diğer C# 3.0 yenilikleri arasında sayılabilir) Söz konusu yeniliğin çıkış amacı genişletilemeyen tiplere yeni fonksiyonelliklerin eklenebilmesinin sağlanmasıdır. Öyleki bu sayede koleksiyonlar(Collections), DataTable, dizi(Array) gibi var olan CLR tipleri(Common Lanugage Runtime) üzerinde LINQ tarzı sorgu ifadelerinin yazılabilmesi olanaklı hale gelmiştir.

Örneğin IEnumerable<T> arayüzüne(Interface) uygulanan genişletme metodları(Extension Methods) sayesinde T türünden koleksiyonlar üzerinde Sum, Count, Select, Average, OrderBy,Distinct gibi fonksiyonellikler uygulanabilmektedir. Bunun için System.Linq isim alanı(Namespace) altında Enumerable isimli static bir sınıf geliştirilmiş ve içerisine aşağıdaki sınıf diagramda(Class Diagram) bir kısmı görünen pek çok genişletme metodu ilave edilmiştir.

Bilindiği üzere SQL sorgularına benzeyen LINQ ifadeleri aslında arka planda metodlar yardımıyla işaret edilebilirler. Nitekim programatik ortamlar bu tarz bir yaklaşımı gerektirmektedir. Üstelik bu işlemler yapılırken var olan tiplerin içeriklerine müdahale edilmemekte, sadece ek fonksiyonellikler katılmaktadır. Bu nedenle genişletme metodları LINQ ifadelerinin kullanılabilmesinde önemli bir role sahiptir. Bu makalemizde genişletme metodlarını derinlemesine incelemeye çalışacak ve ayrıntılara bakıyor olacağız.

Not : Genişletme metodları var olan tiplere ek fonksiyonellikler kazandırılmasını sağlarken bunların orjinal yapısını asla bozmazlar. Tanımlandıkları programda, uygulandıkları tipin bir parçası olarak yaşar ama o tipin orjinalliğine etki etmeden ek işlevselliklerin kullanılabilmesini olanaklı kılarlar.

Herşeyden önce nesne tabanlı programlama dillerinde(Object Oriented Programming Language), kalıtım(Inheritance) sayesinde var olan tiplerin(Type) genişletilmesi mümkündür. Ancak türetilmesine izin verilmeyen tiplerde mevcuttur. Söz gelimi sealed anahtar kelimesi ile imzalanmış olan tipler türetme tekniği yardımıyla genişletilemez. Üstelik .Net içerisinde bu şekilde tanımlanmış olan sayısız sınıf vardır. Örneğin String sınıfı sealed olarak imzalanmış bir sınıf olduğundan kendisinden türetme yapılmasına izin verilmemektedir. (Üstelik String parçalı bir sınıf(Partial Class)' da değildir.)

Bu nedenle bu sınıfa ek fonksiyonellikler ilave edilmesi mümkün değildir. Oysaki var olan .Net tiplerinin(yada kendi geliştirdiğimiz ama türetme yapılmasına izin verilmeyen tiplerin) yapısını bozmadan yeni fonksiyonelliklerin katılarak uygulamalar içerisinde ela alınması istendiği vakalar söz konusudur. Ki nesnelerin SQL tarzında sorgulanabilmeside buna bir örnek olarak verilebilir. Bu sebepten genişletme metodlarının önemi oldukça fazladır.

Not : Genişletme metodları(Extension Methods), değer(value) ve referans(reference) türleri ile arayüzlere(Interface) uygulanabilir. Değer türü olarak yapılar(struct) göz önüne alınabilir. Nitekim yapılar açıkça belirtilmesede kendilerinden türetilme yapılmasına izin vermemektedir.

Genişletme metodları ele alınırken gözden kaçırılmaması gereken bir nokta daha vardır. Genişletme metodları nesne yönelimli programlama jargonundaki kurallardan birisi değildir. Sadece .Net Framework mimarisine özgü bir kavramdır. Genişletme metodları bu anlamda bir tipin paylaşımlı fonksiyonları olarakda düşünülebilir. Hatta bu metodlar .Net Framework 3.5 öncesi sürümlerdeki tipler içinde uygulanabilirdir. Tabi öncelikli olarak genişletme metodlarının(Extension Methods) C# 3.0 içerisinde nasıl yazıldığına bakmata yarar vardır. Genişletme metodlarının yazılmasında üç basit kural vardır. Bu metodlar static bir sınıf içerisinde static olarak tanımlanmalı ve uygulanacakları tipi ilk parametrelerinde this anahtar kelimesi ile birlikte almalıdır.(Ki bunların bir takım sebepleri vardır) Örnek olarak ayrı bir sınıf kütüphanesi içerisinde geliştirilmiş olan aşağıdaki sınıfı göz önüne alabiliriz.

Merkez isimli static sınıfın içeriği aşağıdaki gibidir.

using System;
using System.Drawing;

namespace Genisletmeler
{
    /// <summary>
    /// Genişletme fonksiyonelliklerini içeriri
    /// </summary>
    public static class Merkez
    {
        /// <summary>
        /// Bir string içerisindeki tüm karakterlerin Ascii değerlerini ele alıp byte dizisi şeklinde geriye döndürür. Eğer string null veya empty ise exception döndürür.
        /// </summary>
        /// <param name="s">Byte değerleri döndürülecek string parametre</param>
        /// <returns>Ascii değerleri</returns>
        public static byte[] GetAscii(this string s)
        {
            if (String.IsNullOrEmpty(s))
                throw new Exception("String veri olmalıdır.");
            byte[] result = new byte[s.Length];
            for (int i = 0; i < s.Length; i++)
            {
                result[i] = (byte)s[i];
            }
            return result;
        }

        /// <summary>
        /// Int32 tipinden bir sayının faktöryelinin bulunmasını sağlar
        /// </summary>
        /// <param name="sayi">Faktöryel değeri hesap edilecek değişken</param>
        /// <returns>Sayının faktöryeli</returns>
        public static double Faktoryel(this Int32 sayi)
        {
            if (sayi == 0
                || sayi == 1)
                return 1;
            else
                return  sayi*Faktoryel(sayi-1);
        }

        /// <summary>
        /// İki Point arasındaki uzaklığın pisagor teoremine göre hesap edilmesini sağlar.
        /// </summary>
        /// <param name="nokta1">Birinci nokta</param>
        /// <param name="nokta2">İkinci nokta</param>
        /// <returns>Mesafe</returns>
        public static double Uzaklik(this Point nokta1,Point nokta2)
        {
            int xFarki = nokta1.X - nokta2.X;
            int yFarki = nokta2.X - nokta2.Y;
            return Math.Sqrt((xFarki * xFarki) + (yFarki * yFarki));
        }
    }
}

Merkez isimli static sınıf içerisinde 3 farklı metod yer almaktadır. Bu metodlardan GetAscii String sınıfına, Faktoryel Int32, Uzaklik ise Point yapılarına(Struct) uygulanmaktadır. GetAscii metodu yardımıyla string bir değişkenin karakterlerinin byte tipinden bir dizi olarak elde edilmesi sağlanmaktadır. Faktroyel metodu Int32 tipinden değişkenlere uygulanabilmekte olup, basit olarak sayının faktöryelini hesaplamaktadır. (Üstelik yinelemeli-Recursive bir metod olarak tasarlanmıştır. Buna göre genişletme metodlarının recursive formasyonda kullanılabileceği söylenebilir.) Uzaklik isimli metod ise diğerlerinden farklı olarak birde ek parametre almaktadır. Buna göre Point tipinden bir değişkenin başka bir Point ile arasındaki uzaklığın bulunabilmesi sağlanmaktadır. (Yani kabaca iki nokta arasındaki mesafenin pisagor teoremi çerçevesinde hesaplanması gerçekleştirilmeltedir.) Bir başka deyişle genişletme metodları(Extension Methods) uygulanacakları tipi belirten ilk parametreden sonra ek parametrelerde alabilmektedir. Söz konusu sınıfın özellikle CIL(Common Intermediate Language) tarafına nasıl aktarıldığını incelemeden önce kullanımına bakılabilir. Bu amaçla Merkez isimli static sınıfı içeren kütüphaneyi(Class Library) referans eden basit bir Console uygulaması geliştirilip aşağıdaki kodlar test amacıyla kullanılabilir.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Genisletmeler;
using System.Drawing;

namespace DerinlemesinExtensionMethods
{
    class Program
    {
        static void Main(string[] args)
        {
            // String sınıfı sealed olarak imzalanmıştır bu nedenle kendisinden türetme yapılıp ek fonksiyonellikler katılamaz.
            string ad = "Burak Selim";
            byte[] asciiDegerleri=ad.GetAscii();
            foreach(byte b in asciiDegerleri)
                Console.Write(b.ToString()+" ");
   
            // Int32 bir struct' tır. Struct' lar açıkça belirtilmesede sealed' dır. Yani kendilerinden türetme yapılamaz. Ancak genişletme metodları yardımıyla bunlar ek fonksiyonellikler     katılabilir.
            int sayi = 3;
            Console.WriteLine(sayi.Faktoryel());

            // Point .Net Framework içerisinde System.Drawing isim alanında tanımlanmış olan struct tipidir. Kendisinden türetme yapılamaz. Ancak extension method sayesinde Uzaklik isimli bir metoda sahip olabilir
            Point pn = new Point(10, 20);
            Console.WriteLine("İki Nokta Arası Uzaklık {0}",pn.Uzaklik(new Point(20, 30)).ToString());
        }
    }
}

Uygulama test edildiğinde aşağıdakine benzer sonuçlar ile karşılaşılır.

Görüldüğü gibi string, int ve Point tipinden değişkenler üzerinden yeni fonksiyonellikler kullanılabilmektedir. Peki bu sistem alt tarafta nasıl yürümektedir? Sonuç itibariyle var olan bir CLR tipinin(Common Language Type) bozulmadan genişletilebilmesinin ancak çalışma zamanı motoru(Runtime Engine) tarafından önemli olduğu ortadadır. Hatta dikkat edileceği üzere Visual Studio geliştirme ortamı, eklenen genişletme metodlarını intellisense özelliğinde aynen aşağıdaki ekran görüntüsünde olduğu gibi gösterebilmektedir.

Çalışma zamanında bazı bilgilere bakılması söz konusu ise eğer, niteliklerden(Attribute) yararlanılmaktadır. Bilindiği üzere nitelikler yardımıyla çalışma zamanına ekstra metadata bilgileri aktarılabilmektedir. Bu sebepten genişletme metodlarında da başrol oyuncusu olarak System.Core.dll assembly' ında System.Runtime.CompilerServices isim alanı(Namespace) altında bulunan Extension isimli bir nitelik(Attribute) görev almaktadır. Bu her ne kadar kod tarafında sadece Visual Basic 9.0 ile görülsede, IL tarafında rahat bir şekilde tespit edilebilmektedir.

Yukarıdaki IL görüntüsündende dikkat edileceği üzere GetAscii isimli metod ExtensionAttribute niteliği ile imzalanmıştır. Bu son derece anlamlıdır nitekim hem compiler hemde çalışma zamanı(Runtime) için, takip eden metodun, ilk parametre ile belirtilen tip için bir genişletme olduğu belirtilmektedir. Bir başka deyişle söz konusu nitelik derleyiciye veya çalışma zamanına, ilk parametredeki tip için bazı ek bilgiler gönderir ve yeni fonksiyonelliği kazanmasını sağlar. Buraya kadar genişletme metodlarının ne olduğundan ve nasıl uygulandığından bahsetmeye çalıştık. Genişletme metodları ile ilişkili dikkat edilmesi gereken bazı noktalar da vardır. Dilerseniz yazımızın ilerleyen kısımlarında bu konulara değinelim.

1 - Genişletme metodları aşırı yüklenebilirler(Overloading)

Metodlar aynı isim altında birden fazla kez yazılabilirler(Buna şu an için verilebilecek en güzel örneklerden birisi WriteLine metodudur. Dikkat edileceği üzere bu metodun 19 farklı versiyonu bulunmaktadır.) Bu kısaca metodun aşırı yüklenmesi(Method Overloading) olarak adlandırılmaktadır. Metodun aşırı yüklenmesi sırasındaki önemli kriter, parametre tipleri ve sayılarının belirlediği imzalardır(Method Signature). Çok doğal olarak genişletme metodlarıda aşırı yüklenebilirler. Söz gelimi Merkez sınıfı içerisinde iki nokta arasındaki uzaklığı bulmak için tasarlanmış olan Uzaklik metodunun farklı bir versiyonu aşağıdaki gibi yazılabilir.

/// <summary>
/// İki Point arasındaki uzaklığın pisagor teoremine göre hesap edilmesini sağlar
/// </summary>
/// <param name="nokta1">Metodun uygulanacağı Point tipinden değişken</param>
/// <param name="x2">X2 Değeri</param>
/// <param name="y2">Y2 Değeri</param>
/// <returns>Mesafe</returns>
public static double Uzaklik(this Point nokta1, int x2, int y2)
{
    int xFarki = nokta1.X - x2;
    int yFarki = nokta1.Y - y2;
    return Math.Sqrt((xFarki * xFarki) + (yFarki * yFarki));
}

Bu versiyon diğerinden farklı olarak Point tipinden ikinci bir parametre almak yerine int tipinden iki ayrı parametre kullanmaktadır. Burada metodların hem parametre sayıları hemde tipleri farklılaşmayı sağlamaktadır. Merkez sınıfı bu haliyle örnek uygulamada kullanıldığında aşağıdaki ekran görüntüsünden de izlenebileceği gibi iki farklı Uzaklik metodunun çağırılabileceği görülür.

Burada akla hemen şu soru gelebilir. Merkez sınıfının haricinde başka bir static sınıf içerisinde, Point tipi için Uzaklik metodunun aşırı yüklenmiş başka bir versiyonu yazılabilir mi? Eğer yazılırsa söz konusu uygulamada bu versiyon kullanılabilir mi? Bu sorulara cevap verebilmek için Console uygulaması içerisinde aşağıdaki gibi bir static sınıf tanımlaması yapıldığını varsayalım.

static class Genisletme
{
    public static double Uzaklik(this Point nokta1, double x2, double y2)
    {
        double xFarki = nokta1.X - x2;
        double yFarki = nokta1.Y - y2;
        return Math.Sqrt((xFarki * xFarki) + (yFarki * yFarki));
    }
}

Merkez sınıfı Genisletmeler isim alanı altında ve üstelik farklı assembly içerisinde yer almaktadır. Genisletme isimli sınıf ise DerinlemesineExtensionMethods isimli Console uygulaması içerisinde tanımlanmıştır. Bu durumda Main metodu içerisinde Point tipinden bir değişken kullanılmak istendiğinde Uzaklik isimli fonksiyonun 3 farklı versiyonuna ulaşılabildiği görülecektir.

Bir başka deyişle farklı static sınıflar içerisindede olsalar genişletme metodları aşırı yüklenebilirler.

2 - CLR Tipi(Common Language Runtime Type) içerisinde tanımlı olan bir fonksiyonun aynısı extension method olarak yazılıp, örnek(Instance) tipe ait metod ezilebilir(Override) mi?

Bu bir anlamda orjinal CLR tiplerinin güvenliği ile ilişkilide bir konudur. Nitekim türetilmesine izin verilmeyen tiplerin asıl tasarım amaçlarından biriside içeriklerinin değiştirilmesinin engellenmesidir. Bu anlamda sealed olarak işaretlenmiş tiplerin aslında genişletme metodları yardımıyla ek fonksiyonelliklere sahip olabilmesi ve hatta aşırı yükleme yapılabilmesi orjinal tipte tanımlı metodların ezilip ezilemeyeceği vakasını ortaya çıkarmaktadır. Bu durumu analiz etmek için basit olarak String tipinde tanımlı olan bir metodun aynısını extension method olacak şekilde tanımlamaya çalışabiliriz.

public static string Insert(this string s,int siraNo, string metin)
{
    Console.WriteLine("Extension Method");
    return metin;
}

Burada String sınıfının Insert metodunun aynısı extension metod olarak yazılmaya çalışılmaktadır. Uygulama derlendiğinde herhangibir hata mesajı alınmaz. Ancak string bir değişken üzerinden Insert metodu çağırıldığında aşağıdaki ekran görüntüsünde olduğu gibi orjinal versiyonun kullanılabileceği görülür.

Buna göre derleyici açısından nesne örneği(Object Instance) metodunun daha öncelikli olduğu ortadadır. Bir başka deyişle genişletme metodları yardımıyla orjinal nesne örneğine ait metodlar ezilemezler.

3 - Bir tip içerisinde tanımlı özellik yada alan ile aynı isimde bir extension metod tanımlanırsa.

Bu durumu analiz edebilmek için aşağıdaki kod parçası göz önüne alınabilir.

sealed class Materyal
{
    public int Katsayi;
}
static class Genisletme
{
    public static void Katsayi(this Materyal mtr)
    {
        Console.WriteLine("Genişletme metodu");
    }
}

Burada tanımlanan Materyal isim sınıf sealed olarak imzalanmıştır ve içerisinde int tipinden Katsayi isimli bir alan(Field) içermektedir. Bu tip bir sınıfın başka bir nesne kullanıcısı(Object User) tarafından genişletilmek istendiği bir durumda, bilinçsiz olarak aynı isme sahip genişletme metodları eklenebilir. Bunu sembolize eden Genisletme sınıfı kendi içerisinde, Materyal sınıfındaki alanla aynı adda olan Katsayi isimli bir metod içermektedir. Ne varki kod tarafında Materyal sınıfına ait bir örnek oluşturulduğunda, Katsayi genişletme metoduna erişilemediği açık bir şekilde görülmektedir.

Dikkat edilecek olursa sadece Katsayi isimli nesne alanı(Field) görünmektedir. Fakat burada oldukça enteresan bir durumda söz konusudur. Eğer kodda ısrar edilir ve Katsayi genişletme metodu kullanılmak istenirse derleme zamanı hatası alınmadığı görülür. Hatta kod yürütüldüğünde, genişletme metodunun çalıştığı görülecektir. Bu durum aslında genişletme metodlarının isimlendirilmesinin önemli olduğunu göstermektedir.

4 - Extension metodlar dilden bağımsızdır.

Genişletme metodları daha öncedende bahsedildiği gibi CIL(Common Intermediate Language) tarafında Extension niteliği ile imzalanırlar. Bu sebepten dolayıda .Net destekli diller tarafından kullanılabilirler. Söz gelimi C# kodlaması ile geliştirilmiş genişletme metodları, Visual Basic ile yazılmakta olan bir proje içerisinde kullanılabilir. Elbette tam tersi durumda geçerlidir. Konuyu daha kolay analiz etmek için Merkez isimli sınıfı içeren C# tabanlı kütüphaneyi basit bir Visual Basic Console uygulamasında aşağıdaki gibi deneyebiliriz.

Imports Genisletmeler
Imports System.Drawing

Module Module1

    Sub Main()

        Dim str As String = "Burak Selim Şenyurt"
        Dim dizi As Byte() = str.GetAscii()
        For i As Int32 = 0 To dizi.Length - 1
            Console.Write(dizi(i).ToString() + " ")
        Next

        Console.WriteLine()
   
        Dim sayi As Integer = 4
        Console.WriteLine(sayi.Faktoryel().ToString())
   
        Dim nokta1 As New Point(3, 4)
        Console.WriteLine(nokta1.Uzaklik(6, 8).ToString())

    End Sub

End Module

Visual Basic tarafında kod yazıyor olsakta, Visual Studio arabiriminin intellisense özelliği C# ile yazılmış genişletme metodlarını gösterecektir. Sonuç itibariyle burada yapılan farklı bir assembly içerisinde tip ve üyelerine erişmektir.

Elbetteki kodun çalışabilmesi için C# kütüpanesinin Visual Basic tabanlı projeye referans edilmesi gerekmektedir.

 

Bu işlemin ardından uygulama çalıştırılırsa genişletme metodlarının başarılı bir şekilde çalıştığı görülür.

5 - Object tipinin genişletilmesi.

Object tipide genişletme metodlarına sahip olabilir. .Net Framework içerisinde yer alan tipler object türevli olduklarından çok doğal olarak tanımlanan genişletme metodlarını kullanabilirler. Bu durumu test edebilmek için sembolik olarak aşağıdaki genişletme metodunu eklediğimizi düşünelim.

public static string GetTypeName(this object obj)
{
    return obj.GetType().Name;
}

Metod basitçe herhangibir nesnenin tip adını döndürmektedir. Metodun uygulanışına bakıldığında ise herhangibir tipteki değişkenden sonra çağırılabildiği görülecektir.

Aşağıda, örnek bir kod parçası kullanımı ve çalışma zamanı çıktısı yer almaktadır.

int puan = 51;
Console.WriteLine(puan.GetTypeName());

Point nokta3 = new Point(3, 4);
Console.WriteLine(nokta3.GetTypeName());

string firmaAdi = "FreeLancer";
Console.WriteLine(firmaAdi.GetTypeName());

Visual Basic 9.0' da özellikle Object tipinden bir değişkene atama yapıldığında, genişletme metodlarını çağırmak çalışma zamanı istisnasına(Run Time Exception) neden olmaktadır.

Dim obj As Object = 3.14F
Console.WriteLine(obj.GetTypeName())

Bu kullanım çalışma zamanında MissingMemberException istisnasının(Exception) fırlatılmasına neden olmaktadır. Sorun Object tipinin Late-Bound olmasından kaynaklanmaktadır. Bunun çözmek için type inference kavramından(C# karşılığı var anahtar kelimesi) yararlanılabilir. (Dim obj=3.14F)

Ne varki bu durum C# tarafında geçerli değildir. Bu nedenle C# tarafında aşağıdaki kod parçası sorunsuz olarak çalışmaktadır.

Object obj = 3.14f;
Console.WriteLine(obj.GetTypeName());

Object tipi için genişletme metodları var anahtar kelimesi ile birliktede kullanılabilirler. Aşağıdaki kod parçası bu durumu göstermektedir. Bu kod içerisinde var anahtar kelimesi ile tanımlanan nesne isimli değişken eşitliğin sol tarafı göz önüne alındığında float(Single yapısı-struct) tipindendir. Bu sebepten nesne üzerinden çağırılan GetTypeName isimli genişletme metodu geriye Single değerini döndürecektir.

var nesne = 3.14f;
Console.WriteLine(nesne.GetTypeName());

6 - .Net Framework 2.0 hedefli bir uygulama içerisinde extension metodlar kullanılabilir mi?

Extension niteliği System.Core.dll assembly' içerisinde tanımlanmıştır ve System.Runtime.CompilerServices isim alanında bulunmaktadır. System.Core.dll' i .Net Framework 3.5 ile gelmekte olsada .Net Framework 2.0 motorunu kullanarak çalışmaktadır. Bu noktada .Net 2.0 ile geliştirilmiş bir uygulamada extension metod kullanımı söz konusu olabilir mi? Akla ilk gelen yöntem System.Core.dll assembly' ının ilgili projeye referans edilmesidir. Ancak Visual Studio 2008 içerisinde bu denendiğinde .Net 2.0 tabanlı projeye söz konusu referansların eklenemediği görülecektir.

Browse seçeneği ile ekleme yapılmaya çalışılsada durum değişmeyecektir. Ancak izlenecek basit bir yol ile .Net 2.0 tabanlı projede extension metod kullanımı sağlanabilir. Bunun için uyulamada System.Runtime.CompilerServices isimli bir namespace tanımlanır ve içerisine Extension isimli bir attribute sınıfı eklenir. Bu işlemin ardından extension metod yazılabildiği hatta kullanılabildiği görülecektir. Durumu daha iyi analiz etmek amacıyla .Net 2.0 tabanlı bir Console uygulamasına ait aşağıdaki kod parçası göz önüne alınabilir.

using System;
using System.Runtime.CompilerServices;

// 1nci : İlk olarak System.Runtime.CompilerServices adlı isim alanı içerisinde ExtensionAttribute isimli bir nitelik tanımlanır
namespace System.Runtime.CompilerServices
{
    // 2nci: Nitelik assembly, sınıf ve metod seviyesinde uygulanabilir. Bir kere kullanılabilir.
    [AttributeUsage(AttributeTargets.Assembly| AttributeTargets.Class| AttributeTargets.Method,AllowMultiple=false,Inherited=false)]
    public class ExtensionAttribute :
        Attribute
    {
    }
}
namespace DerinlemesineExtensionMethods2
{
    static class ExtensionMethods
    {
        // Eğer ExtensionAttribute tanımlanmazsa this keyword kullanımı için derleme zamanı hatası alınacaktır.
        public static string GetTypeName(this object obj)
        {
            return obj.GetType().Name;
        }   
        public static double Faktoryel(this Int32 sayi)
        {
            if (sayi == 0
                || sayi == 1)
                return 1;
            else
                return sayi * Faktoryel(sayi - 1);
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            int puan = 12;
            Console.WriteLine(puan.GetTypeName()); // Extension metod kullanımı
       
            int sayi = 4;
            Console.WriteLine(sayi.Faktoryel().ToString());
        }
    }
}

Uygulama çalıştırıldığında genişletme metodlarının işe yaradığı görülebilir.

Tabi bu vaka Visual Studio 2008 üzerinde .Net 2.0 tabanlı bir proje şablonu için gerçeklenmektedir. Nitekim derleme aşamasında sadece C# 3.0 derleyicisi genişletme metodunu değerlendirebilmektedir. Bir başka deyişle Visual Studio 2005 ortamında aynı örnek çalıştırılamayacaktır.

7 - Arayüzlere genişletme metodları eklenebilir.

LINQ(LanguageINtegratedQuery) mimarisinin temelinde yatan genişletme metodlarının çoğu arayüzlere(Interface) uygulanmaktadır. Böylece, genişletme metodlarının uygulandığı arayüz tiplerinden türeyen türlerin tamamı, söz konusu ek fonksiyonellikleri kullanabilir duruma gelmektedir. Bu gerçektende önemli bir yetenektir. Çok doğal olarak geliştirici tarafından yazılmış olan yada Framework içerisinde yer alan arayüz tiplerine genişletme metodları eklenebilir. Aşağıdaki örnek kod parçasında bu duruma örnek olacak bir metod içeriği yer almaktadır.

public static IEnumerable<string> HaricindeKalanlar(this IEnumerable<string> koleksiyon,string aranan)
{
    foreach (string s in koleksiyon)
    {
        if (s != aranan)
            yield return s;
    }
}

HaricindeKalanlar isimli genişletme metodu, IEnumerable<string> tipinden türeyen generic koleksiyonlara uygulanabilmektedir. Görevi parametre olarak verilen string değer dışında kalan elemanları tespit ederek yeni bir IEnumerable<string> tipi içerisinde geriye döndürmektedir.(İşlerin kolaylaştırılmasında .Net 2.0 ile birlikte gelen yield anahtar kelimesinin önemli bir rolü vardır.) Buna göre IEnumerable<string> arayüzünden türeyen her tip, HaricindeKalanlar isimli genişletme metodunu kullanabilmektedir. Söz gelimi aşağıdaki kod parçasında List<string>, Stack<string>, Queue<string> tiplerine uygulanmaktadır.

List<string> isimler = new List<string> { "Burak", "Ahmet", "Mehmet",  "Mehmet", "Ahmet", "Özgür", "Emrah", "Bülent" };
Stack<string> isimler2 = new Stack<string>(isimler);
Queue<string> isimler3 = new Queue<string>(isimler);

var sonuc1=isimler.HaricindeKalanlar("Ahmet");
var sonuc2 = isimler3.HaricindeKalanlar("Ahmet");
var sonuc3 = isimler.HaricindeKalanlar("Mehmet");

Çalışma zamanında örneğin sonuc3 değişkeninin içeriği aşağıdaki ekran görüntüsündeki gibi olacaktır. Dikkat edileceği üzere Mehmet ismi dışında kalanlar elde edilmektedir.

Buraya kadar bahsedilenler kısaca değerlendirilirse, genişletme metodlarının aşağıdaki avantajları sağladığından bahsedilebilir.

  • Var olan tiplere(Types) yeni fonksiyonellikerin eklenebilmesi sağlanır. Öyleki yazılmış olan uygulamaların çalışma sistemini bozmadan yeni fonksiyonellikler katarak genişlemelerine yardımcı olur.
  • Tiplere yeni fonksiyonellikler eklenirken orjinal içeriklerine müdahale edilmesine gerek kalmaz.
  • Özellikle kaynak koda(Source Code) erişilemediği durumlarda ek işlevselliklerin katılabilmesinde önemli rol oynar.
  • Tipleri türeterek genişletmek mümkündür, ancak türetilmelerine izin verilmeyen(Sealed Types) tipler söz konusu olduğunda çözüm genişletme metodlarıdır.

Yinede genişletme metodlarının nesne yönelimli programlama modeli nosyonunun bir parçası olmadığını düşünmekte yarar vardır. Öyleki nesne yönelimli programlama nosyonu göz önüne alındığında, tip genişletmesi aslında türetme ile gerçeklenmektedir. Böylece geldik bir makalemizin daha sonuna. Bu makalemizde kısaca genişletme metodlarını(Extension Methods) derinlemesine incelemeye çalıştık. Bir sonraki makalemizde görüşünceye dek hepinize mutlu günler dilerim.

DerinlemesinExtensionMethods.rar (115,64 kb)

Tags:  
Categories:   C# 3.0
Actions:   E-mail | del.icio.us | Permalink | Yorumlar (0) | Comment RSSRSS comment feed
Bookmark and Share

Bağlantısız Katmanda LINQ

Pazartesi, 2 Nisan 2007 20:44 by bsenyurt

Language Integrated Query (Dil ile tümleştirilmiş sorgu) yardımıyla yapabileceklerimiz saymakla bitmiyor. Aslında LINQ projesinin en önemli çıkış nedeni, Anders Hejslberg' ın anlatımıyla veri ve nesne eşitsizliğidir. (data!=objects) Bu ifadeyi, TechEd 2006 sunumlarında kullanan Anders Hejslberg, özellikle veri yapılarının programlama ortamına alınması sonrasında, var olan basit sorgu tekniklerinin uygulanamayışından yakınmaktadır. LINQ projesinin aslında en temel amacı, uygulamaların çalışma alanlarında (.Net perspektifinden baktığımızda Application Domain' ler içerisinde), bellek üzerinde konuşlanan nesneler üzerinden bildiğimiz veri sorgulama kurallarını uygulayabilmektir. Bir başka deyişle, nesne(object) üzerinde, var olan veritabanı nesnelerini taşıyabilen Entity bileşenleri üzerinde, belleğe alınan Xml veri setleri üzerinde, sorgulamaları bilinen alışılagelmiş söz dizimleri ile tek bir standart altında yapabilmektir. Tüm bu farklı nesnel yapıların ortak bir sorgulama dilini kullanabiliyor olması da LINQ projesinin ana fikirlerinden birisidir aslında.

 

Yukarıdaki grafikte, LINQ projesinin odaklandığı temel modeller ifade edilmeye çalışılmıştır. LINQ sorguları bildiğiniz gibi bellek üzerinde herhangibir şekilde IEnumerable arayüzünü uyarlamış olan her tür nesne topluluğuna uygulanabilmektedir. Bu nedenle bellek içi nesnelerden (in memory objects), veritabanı(database) bağlantılı Entity nesnelerine kadar pek çok yerde kullanılabilmektedir.

C# 3.0 ve geleceği ile ilgili olarak önceki makalelerimizde, DLINQ, XLINQ modellerini incelemeye çalışmıştık. Bunların yanında LINQ ile yapabileceklerimizi daha derinlemesine kavrayabilmek maksadıyla bol bol sorgu geliştirdik. Bugünkü makalemizde ise, özellikle bağlantısız katman (disconnected layer) nesneleri üzerinde, yani bildiğimiz DataSet ve DataTable nesne örnekleri üzerinde LINQ sorgularını nasıl yazabileceğimizi basit bir şekilde incelemeye çalışacağız. DataSet ve DataTable gibi bileşenler bildiğiniz gibi herhangibir veri kaynağından yüklenen sonuç kümelerini uygulama belleğinde tutmak amacıyla kullanılmaktadır. Ne varki çalışma zamanında, bağlantısız katman nesneleri üzerindeki verilerde sorgulama yapabilmek için çeşitli yollara başvurmamız gerekir. Örneğin bunlardan birisi Select metodudur. Bir başka teknikte veri kümelerini DataView bileşenlerine alıp filtreleme amacıyla yardımcı fonksiyonellerden faydalanmaktır. LINQ, felsefe olarak yukarıda bahsettiğimiz tüm veri kümeleri için ortak bir sorgulama ortamı sunmaktadır. Öyleyse bağlantısız katman nesneleri içinde bu tekilleştirilmiş sorgulama modelini nasıl ele alabiliriz?Dilerseniz hiç vakit kaybetmeden örneğimize başlayalım. Bu seferki örneğimizi LINQ Windows Application projesi olarak geliştireceğiz. Nitekim, DataTable içeriğini ekranda görsel olarak ele alabileceğimiz bir ortam olayları daha net algılayabilmemizi sağlayacaktır. Elbetteki bu makalede bahsedilen işlemleri gerçekleştirebilmek için sistemimizde LINQ Preview sürümünün yüklü olması gerektiğini unutmayalım.

Herhangibir DataTable üzerinden LINQ sorguları çalıştırabilmemiz için System.Data.Extensions isimli kütüphanenin program içerisinde referans edilmiş olması yeterlidir. Çalışmakta olduğumuz LINQ Windows uygulaması bu referansı varsayılan olarak içermektedir.

Herşeyden önce uygulamamızın bellek üzerinde DataSet ve DataTable nesnelerine sahip olması gerekiyor. Bu amaçla makalemizde AdventureWorks ve Northwind veritabanlarından yararlanacağız. DataTable nesnelerimizi doldurmak için başvurabileceğimiz iki yol var. Bunlardan birisi, standart Ado.Net tiplerinden ve fonksiyonelliklerinden yararlanmak. Bir başka deyişle, DataAdapter tipi ve Fill metodundan bahsediyoruz. Ancak bu kez biraz daha farklı olarak entity nesnelerinden faydalanacağız. Hatırlarsanız DLINQ konusunu incelediğimiz makalemizde,  bir database ve içerisindeki tablolar için otomatik olarak entity hazırlayabilmemizi sağlayan SqlMetal isimli bir aracın LINQ Preview projesi ile birlikte geldiğinden bahsetmiştik. Bizim için gereken Entity sınıflarını oluşturması için SqlMetal aracını aşağıdaki gibi kullanıp üretilen .cs dosyalarını projemize eklememiz yeterli olacaktır. (SqlMetal aracına, LINQ Preview' u kurduktan sonra, varsayılan olarak D:\Program Files\LINQ Preview\Bin adresinden ulaşabilirsiniz.)

Dolayısıyla artık entity nesneleri üzerinden DataTable nesne örnekleri içerisine veri doldurma işlemini gerçekleştirebiliriz. Yazacağımız ilk kod parçası, AdventureWorks veritabanı içerisinde yer alan Production şemasındaki Product tablosundan bazı satırların bir DataTable içerisine LINQ sorguları yardımıyla alınması işlemini gerçekleştirecektir. Bu amaçla aşağıdaki kod parçasında olduğu gibi AdventureWorks isimli sınıfımıza ait bir nesne örneği oluşturmamız gerekmektedir.

AdventureWorks adWorks = new AdventureWorks("data source=localhost;database=AdventureWorks;integrated security=SSPI");

Artık entity sınıflarımıza ait nesne örneklerini, adWorks üzerinden kullanabiliriz. Aşağıdaki metod ile, global olarak tanımladığımız adWorks nesnesini kullanarak, yine global olarak tanımladığımız dtUrunler isimli DataTable isimli nesne örneğine veri doldurma işlemi yapılmaktadır. Biz LINQ sorgumuz içerisinden belirli alanları alıp yeni bir isimsiz tip (anonymous type) olarak çekmekteyiz. Elbetteki bu sorgu içerisinde bildiğimiz tüm LINQ imkanlarını kullanabiliriz. Where, order by gibi ifadeler bunlara örnek olarak verilebilir.

private DataTable LoadProductsTable()
{
    var urunler =from prd in adWorks.Production.Product
                        select new {
                                            prd.ProductID
                                            ,prd.Name
                                            ,prd.ListPrice
                                            ,prd.Class
                                            ,prd.SellStartDate
                                            ,prd.SafetyStockLevel
                                            ,prd.StandardCost
                                        };

    DataTable dtUrunler=new DataTable("Urunler");
    dtUrunler=urunler.ToDataTable();
    return dtUrunler;
}

Burada ilk olarak adWorks.Production.Product entity nesnesi üzerinden bir LINQ sorgusu çalıştırılmaktadır. Bunun sonucunda elde edilen veri kümesini bir DataTable içerisine aktarmak için ise tek yapılması gereken ToDataTable isimli metodun çağırılmasıdır. (System.Data.Extensions isim alanı, DataTable ve DataRow' lar için LINQ sorguları hazırlanmasını sağlayan pek çok genişletme metodu içermektedir.)

NOT : Kendi örneklerimizi denerken dikkat etmemiz gereken bir nokta vardır. Özellikle null değer alabilen sayısal ve tarihsel formatlı alanlar için LINQ sorguları aşağıdaki ekran görüntüsünde yer alan çalışma zamanı istisnasına neden olabilmektedir. Örneğin Product tablosunda sayısal ve null değer alabilen bir alan olarak tanımlanmış olan ProductSubCategoryID için bu istisna mesajı elde edilmektedir.

Aynı durum null değerler alabilen varchar, nvarchar tipli alanlar için geçerli değildir. Bunların program ortamı içerisinde yer alan entity sınıfları içerisinde string olarak kullanıldığına ve string' in özellikle referans tipi olduğu için null değer taşıyabildiğine dikkat edelim.

Artık elde ettiğimiz DataTable nesne örneğini herhangibir görsel taşıyıcıya (container) bağlayabiliriz. Bu amaçla .Net 2.0 ile gelen DataGirdView kontrolü biçilmiş kaftandır. Uygulamada bu durumu test etmek için ana formumuzun Load olay metodu içerisinde aşağıdaki örnek kod parçaları yazılmıştır.

adWorks = new AdventureWorks("data source=localhost;database=AdventureWorks;integrated security=SSPI");

dtUrunler = LoadProductsTable();
dgUrunler.DataSource=dtUrunler;

label1.Text = "Ürün Sayısı " + (dgUrunler.Rows.Count-1).ToString();

Programın çalışması sonucu aşağıdaki ekran görüntüsünü elde ederiz. Dikkat ederseniz Product tablosundan 504 adet ürün bilgisi yüklenmiştir.

Asıl amacımız elbetteki DataTable nesne örneğini doldurmak değildir. Özellikle şunu tekrar belirtmekte fayda vardır. Örneğimizde Entity tipleri üzerinden veri çekme işlemi yapılmıştır. Pekala bunu DataAdapter yardımıyla da gerçekleştirebiliriz. Ancak asıl yapmak istediğimiz veriyi bağlantısız katmana nasıl aldığımız değil, bellekte veri taşıyan DataTable üzerinden LINQ sorgularını nasıl çalıştırabileceğimizdir. Nitekim LINQ, DataTable veya DataSet içerisine verinin nasıl çekildiği ile ilgilenmez. Bu amaçla örneğin yukarıdaki sonuçları döndüren DataTable bileşenimizin içerisinde üretim tarihi (SellStartDate alanının değeri) bellirli bir zamandan sonra olanları bulmak istediğimizi düşünelim. Söz konusu sorgu için aşağıdaki gibi bir kod parçasını kullanabiliriz.

var sorgulanabilirUrunler = dtUrunler.ToQueryable();

var sonuclar=from prd in sorgulanabilirUrunler
                        where prd.Field<DateTime>("SellStartDate")>=dateTimePicker1.Value
                            select new {
                                                ProductID=prd.Field<int>("ProductID")
                                                ,Name=prd.Field<string>("Name")
                                                ,ListPrice=prd.Field<decimal>("ListPrice")
                                                ,Class=prd.Field<string>("Class")
                                                ,SellStartDate=prd.Field<DateTime>("SellStartDate")
                                                ,SafetyStockLevel=prd.Field<short>("SafetyStockLevel")
                                                ,StandartCost=prd.Field<decimal>("StandardCost")
                                            };

dgUrunler.DataSource=sonuclar.ToDataTable();

label1.Text="Ürün Sayısı "+(dgUrunler.Rows.Count-1).ToString();

Dikkat edeceğimiz ilk nokta ToQueryable metodunun kullanılmasıdır. Bu metodun tek amacı DataTable üzerinde LINQ sorgularının çalıştırılabilmesini sağlamaktır. Aslında ToQueryable, ToDataTable, Field<T> gibi metodlar, System.Data.Extensions.dll içerisinde gelen genişletme metodlarıdır. Bunları görmek için her hangibir decompiler aracını kullanabiliriz. Örneğiz XenoCode Fox 2007 Community Edition aracı yardımıyla System.Data.Extensions.dll içeriğine bakacak olursak aşağıdaki sonuçları alırız.

Gördüğünüz gibi DataTable için ToQueryable ve ToDataTable metodları, DataRow tipi için Field<T> metodu vb... yer almaktadır. Field<T> metodu, sorgulanabilir hale getirlmiş olan DataTable içerisindeki DataRow dizileri üzerinden istenen alanın elde edilebilmesi amacıyla kullanılmaktadır. Dikkat ederseniz generic bir metoddur ve tip olarakta, çekilen alanın veri tipini almaktadır. Bu tipin elbetteki doğru girilmesi şarttır. Aksi takdirde derleme zamanı hataları alırız.

Peki, sorgumuz tam olarak ne yapmaktadır? Tahmin edeceğiniz gibi where anahtar kelimesi sayesinde SellStartDate alanının değeri DateTimePicker kontrolünde seçilen tarihten sonra gelen satırlar çekilmektedir. Buradaki where cümlesinde yer alan prd.Field<DateTime>("SellStartDate")>=dateTimePicker1.Value ifadesinin söz konusu DataTable içerisindeki her bir DataRow için çalıştığını unutmayalım. Bunu daha kolay idrak edebilmek için bu tip bir gereksinimi LINQ olmadan eski usuller ile yazmak istediğinizi düşünün. Tüm satrıları gezeceğimiz bir döngü yazmamız gerektiğini tahmin edebiliriz. Sonuç itibariyle kodumuzu çalıştırdığımızda aşağıdaki veri kümesini elde ederiz.

Sorgularımızı çeşitlendirebiliriz. Öyleki artık elimizdeki nesne, DataTable üzerinden elde edilmiş sorgulanabilir bir DataRow kümesinden başka bir şey değildir ve LINQ ifadelerine doğrudan destek vermektedir. Şimdi işlemlerimizi biraz daha ilerletelim. Örneğin birbiriyle ilişkili olabilen iki DataTable üzerinde LINQ yardımıyla bir Join işlemi gerçekleştirmeye çalışalım. Bu amaçla Northwind veritabanında yer alan Order ve OrderDetails tablolarından faydalalanbiliriz. Öncelikle bu tabloları entity nesnelerimize alacağız ve sonrasında ise DataTable nesne örneklerine yükleyeceğiz. Son olarakta bu iki DataTable örneğine ait sorgulanabilir bir nesne üzerinden LINQ yardımıyla bir Join işlemi gerçekleştireceğiz. Bu amaçla programımıza aşağıdaki metodları ekleyelim.

private DataTable LoadOrdersTable()
{
    var siparisler=from s in north.Orders
                            select new {
                                                s.OrderID
                                                ,s.ShipAddress
                                                ,s.ShipCity
                                                ,s.ShipRegion
                                                ,s.ShipPostalCode
                                                ,s.ShipCountry
                                            };

    return siparisler.ToDataTable();
}

private DataTable LoadOrderDetailsTable()
{
    var siparisDetaylari=from d in north.OrderDetails
                                    select new {
                                                        d.OrderID
                                                        ,d.UnitPrice
                                                        ,d.Quantity
                                                    };
       
    return siparisDetaylari.ToDataTable();

}

Metodlarımız sırasıyla north isimli global olarak tanımlanmış entity nesnesi üzerinden hareket ederek Orders ve OrderDetails tablolarından belleğe bazı alanlar için veri çekmektedir. Son olarak elde edilen sonuç kümeleri ToDataTable metodu yardımıyla geri döndürülüyor. Şimdi bu iki veri kümesininde OrderID alanları üzerinden birbirlerine bağlı olduğunu biliyoruz. Dolayısıyla birleştirme işlemini gerçekleştireceğimiz sorgu cümesinde bu durumu göz önüne almamız gerekiyor. Bu amaçla aşağıdaki gibi bir kod parçasından faydalanabiliriz.

AdventureWorks adWorks;
Northwind north;
DataTable dtUrunler, dtSiparisler, dtSiparisDetaylari;

private void Form1_Load(object sender, EventArgs e)
{
    adWorks = new AdventureWorks("data source=localhost;database=AdventureWorks;integrated security=SSPI");
    north = new Northwind("data source=localhost;database=Northwind;integrated security=SSPI");

    dtUrunler = LoadProductsTable();
    dgUrunler.DataSource = dtUrunler;

    label1.Text = "Ürün Sayısı " + (dgUrunler.Rows.Count - 1).ToString();

    dtSiparisler = LoadOrdersTable();
    dtSiparisDetaylari = LoadOrderDetailsTable();
   
    dgSiparisler.DataSource = dtSiparisler;
    dgSiparisDetaylari.DataSource = dtSiparisDetaylari;
}

private void btnJoin_Click(object sender, EventArgs e)
{
    var sorgulanabilirOrders = dtSiparisler.ToQueryable();
    var sorgulanabilirOrderDetails = dtSiparisDetaylari.ToQueryable();

    var sonuclar=from o in sorgulanabilirOrders
                            join od in sorgulanabilirOrderDetails
                                on o.Field<int>("OrderID") equals od.Field<int>("OrderID")
                                    select new {
                                                        SiparisID=o.Field<int>("OrderID")
                                                        ,BirimFiyat=od.Field<decimal>("UnitPrice")
                                                        ,Miktar=od.Field<short>("Quantity")
                                                        ,Sehir=o.Field<string>("ShipCity")
                                                        ,Ulke=o.Field<string>("ShipCountry")
                                                    };

    dgJoin.DataSource=sonuclar.ToDataTable();
}

LINQ mimarisinde kullandığımız Join kalıbını burada da aynen kullanmaktayız. Tek dikkat etmemiz gereken, generic Field<T> metodunu nasıl ele aldığımızdır. o takma adı ile siparişleri tutan sorgulanabilir DataRow nesne dizisini (sorgulanabilirOrders), od takma adı ilede sipariş detaylarını tutan sorgulanabilir DataRow nesne dizisini (sorgulanabilirOrderDetails) ifade etmekteyiz. Buna göre join işlemini OrderID alanları üzerinden gerçekleştiren ifademiz aşağıdaki gibidir. Burada her iki DataRow dizisindeki ilgili alanların eşitliğine göre bir kıstas getirilmektedir.

on o.Field<int>("OrderID") equals od.Field<int>("OrderID")

Programımızı çalıştırdığımızda aşağıdakine benzer bir ekran görüntüsü ile karşılaşırız. (TabPage' in üst tarafında yer alan iki DataGridView bileşeni, sırasıyla Orders ve OrderDetails bilgilerini göstermektedir.)

Dilersek join ile yazmış olduğumuz sorgumuza where ile başka kısıtlamalarda katabiliriz. Örneğin, elde edilen sonuç kümesinde Quantity alanının değeri 10' un üzerinde olanları elde etmek için tek yapmamız gereken sorgumuzu aşağıdaki gibi genişletmek olacaktır.

var sonuclar=from o in sorgulanabilirOrders
                            join od in sorgulanabilirOrderDetails
                                on o.Field<int>("OrderID") equals od.Field<int>("OrderID")
                                              where od.Field<short>("Quantity")>10

                                    select new {
                                                        SiparisID=o.Field<int>("OrderID")
                                                        ,BirimFiyat=od.Field<decimal>("UnitPrice")
                                                        ,Miktar=od.Field<short>("Quantity")
                                                        ,Sehir=o.Field<string>("ShipCity")
                                                        ,Ulke=o.Field<string>("ShipCountry")
                                                    };

Where ifadesinde ilgili alanın değerinin karşılaştırma işlemine tabi tutmak için yine Field<T> generic metodundan faydalandığımızda dikkat edelim.

İstersek join ile yaptığımız birleştirme işlemini, içerisinde DataRelation nesnesi barındıran bir DataSet üzerinden de gerçekleştirebiliriz. Bu sefer devreye üst tablodaki herhangibir satıra bağlı alt satırların getirilmesini sağlayacak GetChildRows isimli bir fonksiyonellik gelecektir. Durumu daha iyi anlayabilmek için aşağıdaki kod parçasını göz önüne alabiliriz.

DataSet ds = new DataSet();
ds.Tables.Add(dtSiparisler);
ds.Tables.Add(dtSiparisDetaylari);

DataRelation drOrdToDtl = new DataRelation("OrdToDetails", dtSiparisler.Columns["OrderID"],dtSiparisDetaylari.Columns["OrderID"]);
ds.Relations.Add(drOrdToDtl);

var sorgulanabilirOrders=dtSiparisler.ToQueryable();

var sonuclar=from o in sorgulanabilirOrders
                        from od in o.GetChildRows("OrdToDetails")
                            select new {
                                                SiparisID=o.Field<int>("OrderID")
                                                ,BirimFiyat=od.Field<decimal>("UnitPrice")
                                                ,Miktar=od.Field<short>("Quantity")
                                                ,Sehir=o.Field<string>("ShipCity")
                                                ,Ulke=o.Field<string>("ShipCountry")
                                            };

dgJoin.DataSource=sonuclar.ToDataTable();

DataSet içerisinde yer alan, dtSiparisler ve dtSiparisDetaylari isimli DataTable nesnelerinin işaret ettiği veri kümeleri arasındaki ilişkimiz OrderID alanları üzerinden Orders' dan OrderDetails' e doğrudur. Bunu DataSet içerisinde tanımlayan ise Ado.Net' in ilk çıkışından beri bildiğimiz DataRelation nesnesidir. LINQ sorgumuz, bu nesneyi GetChildRows isimli metod içerisinde parametre olarak kullanmaktadır. Böylece o takma adı ile temsil edilen sorgulanabilirOrders içerisindeki her bir DataRow için bu ilişki kullanılabilmektedir. Bu da doğal olarak, ilişkinin diğer ucunda yer alan siparişe ait detay bilgisinin elde edilebilmesi anlamına gelmektedir. LINQ sorgumuz iki adet from anahtar kelimesi içerdiğinden sonuç doğal olarak bir Join sorgusunun çıktısı ile aynı olacaktır. Uygulamamızı bu haliyle çalıştırdığımızda ilk yazdığımız join sorgusundakine benzer sonuçları elde ederiz.

DataTable ve DataSet' ler üzerinde ToQueryable, ToDataTable, Field<T> metodları dışında, LoadSequence, DistinctRows, EqualAllRows, UnionRows, IntersectRows, ExceptRows, SetField<T> isimli metodlarda kullanılabilmektedir. Bu metodların temel amacı, DataTable ve DataRow gibi nesneler üzerinde LINQ tekniklerinin daha da genişletilmesini sağlamaktır. Örneğin LoadSequence metodu sayesinde herhangibir sorgu sonucu elde edilen kümeyi bir var olan bir DataTable içerisine ilave edebiliriz. Bu metod ve diğerleri hakkında daha fazla bilgi almak için LINQ dökümantasyonundan faydalanabilirsiniz.

Böylece geldik bir makalemizin daha sonuna. Bu makalemizde, LINQ' yu DataTable gibi bağlantısız katman nesneleri üzerinde nasıl kullanabileceğimizi incelemeye çalıştık. Bir sonraki makalemizde görüşmek dileğiyle hepinize mutlu günler dilerim.

Örnek Uygulama İçin Tıklayınız.

Tags:  
Categories:   C# 3.0 | LINQ
Actions:   E-mail | del.icio.us | Permalink | Yorumlar (0) | Comment RSSRSS comment feed
Bookmark and Share