C# Generic programlama Giriş I
Bu makalemizde CLR 2.0’ın tanıtılması ile hayatımıza katılan generic programlamaya giriş yapacağız. Generic programlama sayesinde en basit anlamı ile “Bir kez yaz-istediğin kadar kullan” sloganına tam destek vermiş olacağız. Yazdığımız bir kodu sadece int, double yada string türü ile çalışması yerine istediğimz tüm türler ile çalışabilmesini sağlayacağız.
CLR 2.0 öncesinde yazdığımız kodun farklı türdeki parametreler ile çalışmasını da istediğimizde zorunlu olarak Object tipini kullanıyorduk. C#’ta tüm nesneler Object türünden türediğinden dolayı object parametresine sahip olan bir methodu farklı türdeki parametreler ile çağırabiliyorduk. Bu şekilde yazılmış bir kodda aşağıdaki sorunların oluşma ihtimali yüksekti;
· derleme zamanında oluşabilecek tür dönüşümleri hatalarını
· Extra tür dönüşümü maliyetleri (boxing – unboxing)
· Yazdığımız sınıf/method kodlarını farklı türler ile kullanamama(Object tipi bir noktaya kadar farklı tiplere destek verdiğinden dolayı)
CLR 2.0’ın generic programlamayı desteklemesi ile birlikte bu gibi senaryolarda Object sınıfını kullanma zorunluluğu da ortadan kalkmış oldu. Artık T tipini parametre alan bir generic sınıf oluşturduğumuzda, bu sınıfın kullanıldığı yerlerde çalışma zamanında karşılaşabileceğimiz tür dönüşümleri sorunlarını ya da Object türüne yapılan dönüşüm(boxing) maliyetlerini düşünmemiz gerekmeyecektir.
Generic programlama mantığı hakkında bu kadar konuştuktan sonra, generic kullanımının bize getirecekleri inceleyelim;
Performans
Generic programlamanın en büyük avantajlarından birisi performanstır. Generic olmayan sınıflarda kullanılan referans tiplerinin Object türüne dönüşümü(boxing) veya Object türünden referans türüne dönüşümü (unboxing) maliyetli işlemlerdir. Generic programlamada ise oluşturduğumuz sınıfa ilk aşamada hangi türde parametre alacağımızı dışarıdan belirttiğimizden dolayı tür dönüşümü maliyetlerinden otomatik olarak kurtulmuş oluyoruz.
Bu konuyu generic olmayan ArrayList sınıfı üzerinde inceleyebiliriz. ArrayList sınıfı farklı türdeki nesneleri tek bir yerde tutmamıza olanak sağlamaktadır ve yeni bir eleman ekleyebilmemiz için Add() methodu bulunmaktadır. ArrayList’e değer tipinden(int,double vb.) yeni bir eleman eklendiğinde eklenen eleman object türüne dönüştürülerek eklenmektedir. Aynı şekilde ArrayList’ten okunan bir değer için ise Object türünden değer tipine dönüşüm yapılmaktadır. Örnek üzerinde inceler isek;
ArrayList lst = new ArrayList();
lst.Add(27); // boxing -- değer tipinden referans tipine dönüşüm yapılıyor
int yas = (int)lst[0]; //unboxing -- referans tipinden değer tipine dönüşüm yapılıyor
foreach(int eleman in lst)
{
Console.WriteLine(eleman);//her okunan eleman için unboxing yapılıyor
}
Boxing ve unboxing kullanımı kolay olmasına rağmen performans konusunda büyük sorun oluşturmaktadır. Özellikler bir collection içerisinde iterasyon yapılıyor ise tüm elemanlar tek tek unboxing işlemine tabi tutulacaktır.
ArrayList kullanmak yerine generic yapıya sahip List<T> sınıfının kullanımı boxing ve unboxing sorununu ortadan kaldıracaktır.
List<int> lst = new List<int>();
lst.Add(27); // boxing oluşmuyor.değer tipi collection’da tutuluyor
int yas = lst[0]; // unboxing oluşmuyor.tür dönüşümüne gerek yok
foreach (int eleman in lst)
{
Console.WriteLine(eleman);
}
Tür Güvenliği
Generic kullanımının diğer bir faydası ise tür güvenliğidir. ArrayList kullanımı ile collection’a herhangi bir türden eleman eklemesi yapılabilir.
ArrayList lst = new ArrayList();
lst.Add(27);
lst.Add("herhangi bir string");
lst.Add(new myClass());
Bu collection üzerinde foreach ile int bir eleman kullanarak iterasyon yapmaya çalışırsak, derleyici herhangi bir hata vermeyecektir.
foreach(int eleman in lst)
{
Console.WriteLine(eleman);
}
Ancak collection’da bulunan tüm elemanlar int türünden olmadığından dolayı çalışma zamanında bir hata ile karşılaşacağız.
Generic List<T> kullanımı ile yapılacak olan hata çok daha erken fark edilebilecektir çünkü List<T> collection’ı sadece T türünden eleman eklemelerine izin verecektir ve farklı türden bir eleman eklemesinde derleyici bize uyarı verecektir.
List<int> lst = new List<int>();//sadece int türünden elemanların tutulacağı collection
lst.Add(27);
lst.Add("herhangi bir string");//tür uyumsuzluğundan dolayı HATA
lst.Add( new myClass());//tür uyumsuzluğundan dolayı HATA
Generic Sınıf Oluşturma
Generic bir sınıf normal bir sınıfa benzer bir şekilde tanımlanmaktadır. Bunun yanında hangi tür ile kullanılacağını belirten bir generic tip tanımı içermektedir. Verdiğimiz bu generic tip ile sınıfın üyelerinin hangi tipte olacağını ve sınıfın methodlarıının parametre olarak hangi tipi kabul edeceğini belirleyebiliriz. Aşağıdaki örnekte bu özelliği görebiliriz;
public class myGenericPair<T> //<T> generic tip tanımı yapmaktadır.
{
private T genericMember;//generic üye
private int value;//generic olmayan üye
//Consttructor
public myGenericPair(T genericMember, int value)
{
this.genericMember = genericMember;
this.yas = value;
}
//generic bir türde dönüş değerine sahip method
public T getGenericValue()
{
return genericMember;
}
}
Oluşturduğumuz sınıfı ise şu şekilde kullanabiliriz;
myGenericPair<string> genericSinifim = new myGenericPair<string>("Ali Can",5);
genericSinifim.getGenericValue();
Görüldüğü gibi <T> yerine sınıfı hangi generic açılımını kullanacak isek T türünde onu belirtiyoruz.Üstteki örnekte myGenericPair sınıfının string açılımı kullanılmıştır.
Generic Sınıfların Özellikleri
generic sınıflar kullanılır iken bazı koşullara ihtiyaç duyabiliriz. Örneğin generic bir tipe null değeri atamak mümkün değildir. Bunu yapabilmek için “default” anahtar kelimesi kullanılabilir ya da kullanılacak olan generic tipin bazı özelliklere sahip olmasını istediğimiz durumlarda (T tipinin belirli bir interface’i uygulaması istediğimizde ya da T tipini belirli bir türden türemesini istediğmizde) sınırlandırmalar kullanılabilir.
Default anahtar kelimesi(null değer atama)
Defult anahtar kelimesi en basit tanımı ile T türüne 0( T değer türü ise) ya da “null”(T referans türü ise) atamakta kullanılır. Örneğin bir banka otomasyonu yapan bir programda sıradaki müşteriler içerisinden bir müşteriyi geri döndüren bir methoda sahip olduğumuzu düşünelim. Eğer kuyrukta müşteri yok ise null değeri döndürülsün.
public T getNextCustomer()
{
T musteri = default(T);
T = kuyruk.Dequeue();
return T;
}
Kısıtlamalar
Generic sınıfımızda kullanılacak T tipiniz bazı koşulları sağlaması istediğimiz durumlarda kısıtlamalar çok faydalı olacaktır. Örneğin T tipinin değer tipi olmasını istediğiz aşağıdaki gibi bir kısıtlama yazmamız yeterli olacaktır.
public class myGeneric<T, U>
where T : struct // T parametresi değer tipinde olmalı
where U : class // U parametresi referans tipinde olmalı
{….}
Aşağıdaki tabloda 6 tipte yapılabilen kıstılamalar bulunuyor;
|
Kıstılama
|
Tanım
|
|
where T : struct
|
T tipi değer tipinde olmalı.(Nullable dışındaki tüm değer tipleri olabilir)
|
|
where T : class
|
T tipi referans tipinde olmalı
|
|
where T : new()
|
T tipi public parametresiz constructor’a sahip olmalı
|
|
where T : <temel sınıf adı>
|
T tipi belirli bir sınıftan türemeli
|
|
where T : <interface adi>
|
T tipi belirli bir interface’i uygulamalı
|
|
where T : U
|
T tipi U tipinden türemeli ya da U tipinde olmalı
|
Static Üyeler
Generic sınıflarda static üyelere özellikle dikkat edilmeli. Çünkü T tipine göre oluşan her sınıfın static üyeleri sadece kendi aralarında paylaşılır.
Yani myClass<string> ile myClass<int> açılımlarının static üyeleri birbirinden farklı değerlere sahiptir. Örneğin;
public class myClass<T>
{
public static int x;
}
myClass<string>.x = 5;
myClass<int>.x = 7;
Console.WriteLine(myClass<string>.x);//Console 5 yazılıyor
Sonraki makalemde generic methodlar,Interface,delegateler konuları inceleceğim. Başka bir makalede görüşmek üzere…