C#.NET
Bildiğiniz gibi C# dili 2001 yılında Microsoft tarafından çıkarılan ve nesne yönelimli programlama tekniğine %100 destek veren bir programlama dilidir. C#, programcılara sunulduğundan beri bir çok programcının dikkatini çekmiştir. Bu ilgide en önemli neden herhalde C# dilinin kendinden önce çıkarılmış olan JAVA ve C++ dillerini örnek almasıdır. Evet C# modern çağın gerektirdiği bütün yazılım bileşenlerini içermekle beraber eski programlama dillerinde bulunan iyi özellikleri de yapısında barındırmaktadır. Microsoft ve C# dil tasarımcıları her geçen gün yeni piyasa araştırmaları yaparak dile katabilecekleri özellikleri tartışmaktadırlar. Bu amaçla C# dilinin tasarımcıları yakın bir zaman içinde C# diline eklemeyi düşündükleri yeni özellikleri bildirmişlerdir. Bu yazıda muhtemelen "VS.NET for Yukon(VS.NET Everett'ten sonraki versiyon)" ile birlikte uygulamaya konulacak C# dilinin muhtemel özelliklerini özetlemeye çalışacağım. Bu bildirinin tamamını C# topluluğunun resmi sitesi olan www.csharp.net adresinden okuyabilirsiniz.
C# diline yakın bir zamanda eklenilmesi düşünülen özellikler 4 ana başlık altında toplanmıştır. Bu özellikler temel olarak aşağıdaki gibidir.
1 - Generics (Soysal Türler)
2 - Iterators
3 - Anonymous Methods (İsimsiz-Anonim- Metotlar)
4 - Partial Types (Kısmi Türler)
Bu yazıda yukarıda başlıklar halinde verilen her bir konuyu ayrıntılı olarak inceleyip, programcıya ne gibi faydalar sağlayabileceğini ve programların performansına nasıl etki edeceğine değineceğim.
1 - Generics
Profesyonel programlamada, türden bağımsız algoritma geliştirme önemli bir tekniktir. Türden bağımsız algoritmalar geliştirici için büyük kolaylıklar sağlamaktadır. Söz gelimi iki int türden sayının toplanmasının sağlayan bir fonksiyonu yazdıktan sonra aynı işlemi iki double türden sayı için tekrarlamak zaman kaybına sebep olacaktır. C++ dilinde türden bağımsız algoritma kurabilmek için şablon(template) fonksiyonları ve şablon sınıfları kullanılmaktadır. C#, türden bağımsız algoritma geliştirmeye doğrudan destek vermiyor olsada dolaylı yollardan türden bağımsız işlemler yapabilmek mümkündür. Bu işlemler C#'ta "Her şey bir Object'tir" cümlesinin altında yatan gerçekle halledilmektedir. C#'ta herşeyin bir nesne olması ve her nesnenin ortak bir atasının olması ve bu atanın da Object sınıfı olması bu cümlenin altında yatan gerçektir. Dolayısıyla herhangi bir türe ait referansı Object referasnlarına ataybiliriz. Yani bir bakıma türden bağımsız bir işlem gerçekleştirmiş oluyoruz. Söze gelimi Object türünden bir parametre alan bir fonksiyonu dilediğimiz bir nesne referansı geçebiliriz. Temel(base) sınıfa ait referanslara türeyen(inherited) sınıf referanslarını ataybilmek nesne yönelimli programlama tekniğinin sunduğu bir imkandır.
C#'ta Object referanslarına istenilen türden referanslar atanabilir. Bu, büyük bir imkan gibi görünsede aslında bazı dezavantajlarıda beraberinde getiriyor. Çünkü çalışma zamanında Object türüne atanmış referanslar orjinal türe tekrar geri dönüştürülmektedir. Kısaca unboxing olarak bilinen bu işlem özellikle değer(value) ve referans(reference) türleri arasında yapıldığında önemsenecek büyüklükte bir performans kaybı meydana gelmektedir. Çünkü değer ve referans türleri belleğin farklı bölgelerinde saklanmaktadır. Bu durum boxing ve unboxing işlemlerinin çalışma zamanında farklı bellek bölgeleri arasında uzun sürebilecek veri transferlerine sebep olur. Bu tür bir performans kaybını bazı veri yapıları için önlemek için C# dil tasarımcıları Generics isimli bi kavramın dile eklenmesini öngörmüşlerdir. Bu sayede bazı veri yapılarında özellikle .NET sınıf küyüphanesindeki System.Collections isim alanında bulunan veri yapılarında epeyce performans kazancı elde edilecektir.
İsterseniz basit bir yığın(stack) sınıfı üzerinden "generics" kavramının sağlayacağı yaraları ve boxing/unboxing işlemlerinin etkisini inceleyelim.
.NET sınıf kütüphanesinde de bulunan Stack sınıfı içinde her türden veri bulunduran ve istenildiğinde bu verilere LIFO(son giren ilk çıkar) algoritmasına göre veri çekilebilen bir veri yapısdır. .NET'teki Stack sınıfı ile bütün veri türlerine ait işlemleri yapabilmek için Stack sınıfındaki veri yapısı Object olarak seçilmiştir. Eğer bu böyle olmasaydı Stack sınıfının her bir tür için ayrı ayrı yazılması gerekecekti. Bu mümkün olsa bile herşey bitmiş olmayacaktı. Çünkü Stack sınıfı kullanıcını tanımlayacağı türleri barındıracak duruma gelmez. İşte bütün bu sebeplerden dolayı Stack veri yapısında saklanan veriler Object olarak seçilmiştir. Buna göre Stack sınıfının arayüzü aşağıdaki gibidir.
class Stack public void Push(object veri) |
Bildirilen bu Stack sınıfının elemanı Object türünden olduğu için Push() metodu ile istediğimiz türden veriyi saklayabiliriz. Aynı şekilde Pop() metodu ile bir veri çekileceği zaman veri Object türünden olacaktır. Pop() metodu ile elde edilen verinin gerçek türü belli olmadığı için tür dönüştürme operatörü kullanılır. Örneğin,
Push(3); |
şeklinde yığına eklenen veriyi tekrar elde etmek için
int a = (int)Pop(); |
biçiminde bir tür dönüşümü yapmamız gerekir. Bu işlemler kendi tanımlayacağımız özel sınıflar içinde geçerlidir. Ancak int ve double gibi temel veri türlerindeki performans kaybı daha fazladır. Çünkü Push(3) şeklindeki bir çağrımda boxing işlemi gerçekleşirken Pop() metodunun çağrılmasında unboxing işlemi gerçekleşir. Üstelik bu durumda Pop() metodunun geri dönüş değerini byte türüne dönüştürmeye çalışırsak derleme zamanında herhangi bir hata almayız. Bu da çalışma zamanında haberimiz olmadan bazı veri kayıplarının olabileceğini gösterir. Kullanıcı tanımlı sınıflar ilgili bir yığın kullanıyorsak Pop() metodunun geri dönüş değerini farklı bir kullanıcı tanımlı sınıfa çaviriyorsak bu sefer de derleme zamanında hata alınmaz, ancak çalışma zamanında "invalid cast operation" istisnai durumu meydana gelir.
Bütün eksi durumlardan kurtulmak için generics(soysal tür)'lerden faydalanılabilir. Soysal türler C++ dilindeki şablon sınıflarının bildirimi ile benzerdir. Bu tür sınıf bildirimlerine parametreli tip de denilmektedir. Parametreli tipler aşağıdaki gibi bildirilir.
class Stack public void Push(Veri türü veri) |
ile stack sınıfnın hangi türden verileri tutacağı stack nesnesini oluşturacak programcıya bırakılmıştır. Örneğin int türden verileri saklayacak bir yığın aşağıdaki gibi oluşturulur.
Stack<int> yıgın = new Stack<int>; |
Yukarıdaki şekilde bir yıgın oluştrulduğunda Stack sınıfınuın bildirimindeki Veri türü ifadeleri int türü olarak ele alınacaktır. Dolayısıyla Pop() metodu ile yığından bir eleman çıkarılıp aşağıdaki gibi başka bir değişkene atanmak istendiğinde tür dönüştürme operatörünü kullanmaya gerek yoktur. Bu da boxing ve unboxing işlemlerinin gerçekleşmediği anlamına gelir ki istediğimiz de buydu zaten.
Stack<int> yıgın = new Stack<int>; |
Aynı şekilde yığınımızın double türden verileri saklamasını istiyorsak int yerine double kullanmalıyız. Bu durumda çalışma zamanında hem int hem de double verileri tutan yığın sınıfları oluşturulacaktır. Biz tek bir yığın sınfı bildirmiş olmamıza rağmen çalışma zamanı bizim için ayrı iki yığın sınıfı oluşturur.
Soysal türleri kendi tanımladığımız sınıflar içinde oluşturabiliriz. Örneğin Musteri isimli bir sınıfın verilerini yığında tutmak için yığın sınıfını aşağıdaki gibi oluşturmalıyız.
Stack yıgın = new Stack; |
Bu durumda yığına sadece Musteri nesneleri eklenebilir. Yani yıgın.Push(3) şeklindeki bir kullanım derleme aşamasında hata verecektir. Aynı zamanda yığından çekilecek veriler de Musteri türündendir. Dolayısıyla tür dönüşümü uygun türler arasında olmalıdır.
Yığın sınıfı yukarıda anlatılan şekilde kullanıldığında yığındaki elemanların belirli bir türden olduğu garanti altına alınır. Böylece Musteri türünden nesneleri tutan bir yığına "3" gibi bir sayıyı ekleyemeyeceğimiz için daha gerçekçi programlar yazılır.
Stack örneğinde sadece bri tane parametre türü kullandık. Soysal türlerde istenilen sayıda parametreli tür kullanılabilir. Örneğin Hashtable sınıfnındaki Deger ve Anahtar ikilisi aşağıdaki gibi parametreli tür olarak bildirilebilir.
public class Hashtable |
Yani bir Hashtable nesnesi oluşturulacağı zaman her iki parametre türü de belirtilmelidir. Örneğin Anahtar türü int olan ve değer türü Musteri sınıfı olan bir Hashtable nesnesi aşağıdaki gibi oluşturulabilir.
Hashtable<int,Musteri> hashtable = new Hashtable<int,Musteri>; |
Not : Parametre sayısını aralarına virgül koyarak dilediğimiz kadar artırabiliriz.
Soysal türlerin saydığımız avantajlarının yanında bu haliyle bazı dezavantajları ve kısıtlamalarıda vardır. Söz gelimi Hashtable sınıfının bildirimi içinde AnahtarTuru verisinin bazı elemanlarını bir ifade de kullanmak istiyoruz; derleyici hangi AnahtarTuru parametrelei türünün hangi türden olduğunu bilmediği için bu durumda sadece Object sınıfının ait metotlar ve özellikler kullanılabilir. Mesela Hashtable sınıfının Add metodu içinde anahtar parametresi ile CompareTo() metodunu kullanmak istiyorsak CompareTo metodunun bildirildiği IComparable arayüzünü kullanarak aşağıdaki gibi tür dönüşümü yapmalıyız.
public class Hashtable |
Hashtable sınıfının Add() metodu yularıdaki şekilde bildirilse bile hala eksik noktalar var. Mesela AnahtarTuru parametresi eğer gerçekten IComparable arayüzünü uygulamıyorsa switch ifadesi içinde yapılan tür dönüşümü geçersiz olacaktır ve çalışma zamanında hata oluşacaktır. Çalışma zamanında meydana gelebilecek bu tür hataları önlemek için yapılabilecek tek şey AnahtarTuuru parametresinin IComparable arayüzünü uyguluyor olmasını zorlamaktır. Bu işlemi yapmak için AnahtarTuru parametresine çeşitli kısıtlar(constraints) getirilir. Aşağıdaki Hashtable sınıfında AnahtarTuru parametresinin IComparable arayüzünü uygulaması gerektiği söylenmektedir. Bu kısıt için where anahtar sözcüğü kullanılır.
public class Hashtable<AnahtarTuru, DegerTuru> where AnahtarTuru : IComparable |
Dikkat ettiyseniz uygulanan kısıttan sonra switch ifadesi içinde anahtar değişkeni üzerinde tür dönüşümü işlemi yapmaya gerek kalmamıştır. Üstelik kaynak kodun herhangi bir noktasında Hashtable nesnesini IComparable arayüzünü uygulamayan bir AnahtarTuru parametresi ile oluşturursak bu sefer ki hata derleme zamanında oluşacaktır.
Not : parametreli türler üzerindeki kısıt sadece arayüz olmak zorunda değildir. Arayüz yerine sınıflar da kısıt olarak kullanılabilir.
Bir parametreli türe birden fazla arayüz kısıtı konabileceği gibi aynı sınıftaki diğer parametreleri türler için de kısıt konulabilir. Ancak bir parametreli tür için ancak sadece bir tane sınıf kısıt olabilir. Örneğin aşağıdaki Hashtable sınıfında DegerTuru Musteri sınıfından tremiş olması gerekirken, AnahtarTuru hem IComparable hemde IEnumerable arayüzünü uygulamış olması gerekir.
public class Hashtable<AnahtarTuru, DegerTuru> where |
2 - Iterators
Bir dizinin elemanları üzerinde tek tek dolaşma işlemine iterasyon denilmektedir. Koleksiyon tabanlı nesnelerin elemanları arasında tek yönlü dolaşmayı sağlayan foreach döngü yapısının bizim tanımlayacağımız sınıflar için de kullanılabilmesi için sınıfımızın bazı arayüzleri uyguluyor olması gerekir. foreach döngüsü derleme işlemi sırasında while döngüsüne dönüştürülür. Bu dönüştürme işlemi için IEnumerator arayüzündeki metotlardan ve özelliklerden faydalanılmaktadır. Bu dönüştürme işleminin nasıl yapıldığına bakacak olursak :
ArrayList alist = new ArrayList(); |
foreach döngüs yapısı için gerekli olan arayüzlerin uygulanması özellikle ağaç yapısı şeklindeki veri türleri için oldukça zordur. Bu yüzden C# sınıfların foreach yapısı ile nasıl kullanılacağına karar vermek için yeni bir yapı kullanacaktır.
Sınıflarda, foreach anahtar kelimesi bir metot ismi gibi kullanılarak sınıfın foreach döngüsünde nasıl davranacağını bildirebilriz. Her bir iterasyon sonucu geri döndürülecek değeri ise yield anahtar sözcüğü ile belirtilir. Örneğin her bir iterasyonda farklı bir tamsayı değeri elde etmek için sınıf bildirimi aşağıdaki gibi yapılabilir.
public class Sınıf |
Yukarıda bildirilen Sınıf türünden nesneler üzerinde foreach döngüsü kullanıldığında iterasyonlarda sırasıyla 3,4 ve 5 sayıları elde edilecektir. Buna göre aşağıdaki kod parçası ekrana 345 yazacaktır.
Sınıf deneme = new Sınıf(); |
Çoğu durumda foreach yapısı ile sınıfımızın içindeki bir dizi üzerinde iteratif bir şekilde dolaşmak isteyeceğiz. Bu durumda foreach bildirimi içinde ayrı bir foreach döngüsü aşağıdaki gibi kullanılabilir.
public class Sınıf |
Yukarıdaki Sınıf nesneler ile foreach döngüsü kullanıldığında her bir iterasyonda elemanlar dizisinin bir sonraki elemanına ulaşılır.
Gördüğünüz gibi programcının bildireceği sınıflar da foreach döngüs yapısını kullanabilmek için eskiden olduğu gibi IEnumerator arayüzün uygulamaya gerek kalmamıştır. Bu işlemi derleyici bizim yerimize yapar.
3 - Anonymous Metotlar(İsimsiz Metotlar)
İsimsiz metotlar, bir temsilciye ilişkin kod bloklarını emsil eder. Bildiğiniz gibi temsilciler yapısında metot referasnı tutan veri yapılarıdır. Bir temsilci çağrımı yapıldığında temsilcinin temsil ettiği metot çalıştırılır. Özellikle görsel arayüzlü programlar yazarken event tabanlı programlama tekniği kullanılırken temsilcilerin kullanımına sıkça rastlanır. Örneğin bir Button nesnesine tıklandığında belirli bir kod kümesinin(metot) çalıştırılması için temsilci veri yapısından faydalanılır. Sözgelimi Button nesnesinin tıklanma olayı meydana geldiğinde Click isimli temsilcisine yeni bir temsilci atanır. Ne zaman button nesnesinin Click olayı gerçekleşse ardından hemen temsilcinin temsil ettiği metot çağrılır. Buna bir örnek verecek olursak;
public class Form |
Yukarıdaki koddan da görüldüğü üzere temsilci ile temsilcinin temsil ettiği metotlar ayrı yerlerdedir. İsimsiz metotlarla bu işlemi biraz daha basitleştirmek mümkündür. Temsilci oluşturulduktan sonra açılan ve kapanan parantezler arasına temsilci çağrıldığında çalıştırılacak kodlar yazılabilir. Yukarıdaki örneği isimsiz metot ile yapacak olursak :
public class Form |
Tanımlanan kod bloğundan sonra noktalı vürgülün eklenmiş olduğuna dikkat edin. Temsilci bloğundaki kodlar normal metotlardan biraz farklıdır. Normal kod blokları ile benzer özellikler taşır. Yukarıdaki temsilci kod bloğunda, blok dışında tanımlanan değişkenlere erişebilmek mümkündür. Ayrıca olay argümanlarının da(sender,e) EventHandler türünün parantezleri içinde yazıldığınıda dikkat edin. Bir önceki versiyonda olay argümanlarının yerine temsil edilen metodun ismi yazılmıştı.
Peki isimsiz metotlar nasıl çalıştırılmaktadır? İsimsiz metot tanımı ile karşılaşan derleyici tekil isme sahip bir sınıf içinde tekil isme sahip bir metot oluşturur ve isimsiz metot gövdesindeki kodlara bu tekil metot içinden erişilir. Temsilci nesnesi çağrıldığında, derleyicinin ürettiği bu metot ile isimsiz metodun bloğundaki kodlar çalıştırılır.
4 - Partial Types (Kısmi Türler)
Kısmi türler yardımıyla bir sınıfın elemanlarını farklı dosyalarda saklamak mümkündür. Örneğin Dosya1.cs ve Dosya2.cs aşağıdaki gibi olsun.
//Dosya1.cs |
//Dosya2.cs |
Yukarıdaki iki dosyayı aynı anda derlediğimizde eğer kısmi türler kavramı olmasaydı derleme zamanında hata alırdırk. Çünkü aynı isim alanında birden fazla aynı isimli sınıf bildirimi yapılmış. Halbuki kısmi türler ile bu iki sınıf bildirimi aynı sınıf olarak ele alınır, ve birleştirilir. Yani deneme isimli sınıfın Metot1() ve Metot2() adında iki tane metodu olmuş olur.
Bir türe ait elemanları tek bir dosya içinde toplamak Nesne Yönelimli Programlama açısından her ne kadar önemli olsada bazen farklı dosyalarla çalışmak kodlarımızın yönetilebilirliğini artırabilmektedir.
Not : Bu yazı "MSDN Magazine" deki "Future Features of C#" başlıkla bildiri baz alınarak hazırlanmıştır.
Bu günlerde hepimiz .Net Framework 3.0 ve getirileri üzerine yoğunlaşmış durumdayız. Özellikle mimari anlamda yapılan köklü değişimler söz konusu. Bu köklü değişiklikler; Windows uygulamalarının yeni yüzü olan WPF (Windows Presentation Foundation) ve XAML (eXtensible Application Markup Language), dağıtık mimariyi tek çatı altında toplamayı başaran WCF (Windows Communication Foundation), akış şemaları ve iş süreçlerinin .Net plaformuna dahil edilmesini sağlayan WF(Workflow Foundation) ve CardSpace olarak sıralanabilir. Ancak bunların dışında Microsoft’ un gelecek vizyonu içerisinde yer alan en önemli konulardan biriside C# 3.0 konusudur. Bildiğiniz gibi C#, sıfırdan geliştirilmiş ve atası olan nesne yönelimli dillerin en iyi özelliklerini bünyesinde birleştirerek bunu güçlü bir Framework üzerinde kullanabilmemizi sağlayan bir dildir. Zaman içerisinde C# 2.0 ile gelen yenilikler şu anda tüm C# geliştiricilerin hayatının bir parçası haline gelmiştir. Şimdi herkesin gözü C# 3.0 üzerinde.
C# 3.0, beraberinde LINQ (Language Integrated Query), DLINQ (Database Language Integrated Query) ve XLINQ (Xml Language Integrated Query) gibi yeni teknolojileride getirmekte ve desteklemektedir. Biz bu makalemizde daha fazla LINQ ifadesi yazmaya çalışacağız. Onbir basit LINQ ifadesi ile dil tabanlı sorguları daha yakından tanımaya başlıyacak ve elimizdeki gücün farkına varacağız. Bildiğiniz gibi LINQ (Language Integrated Query) özellikle dil içerisinde, Sql tarzı sorgular yazabilmemizi ve bunları var olan IEnumerable<T> türevli tipler üzerinde kullanabilmemizi sağlamaktadır. Ancak özellikle LINQ içerisinde kullanılabilen operatörler göz önüne alındığında, oldukça etkili sonuçlar alabileceğimiz ortadır. Temel olarak LINQ içerisindeki operatörler aşağıdaki başlıklar altında toplanmıştır. (Elbetteki bu bilgiler hala deneme aşamasında olan bir sürece aittir ve değişebilir.)
Şimdi gelin bu operatörlerin bir kısmını incelemeye çalışalım. Öncesinde program ortamında ele alabileceğimiz bazı veri kümelerine ihtiyacımız olacak. Bu veri kümeleri tamamıyla test amaçlı olacaktır. Bunun için AdventureWorks veritabanında yer alan Product ve ProductSubCategory tablolarından faydalanabiliriz. Amacımız ilk olarak buradaki tablolardan test amacıyla kullanabileceğimiz veri kümelerini program ortamı içerisinde yer alan generic koleksiyonlara aktarmaktır. LINQ konusu söz konusu olduğu içinde, C# 3.0 dili özelliklerinden de faydalanmaya çalışacağız.
Yardımcı sınıfımızın kodları aşağıdaki gibidir.