Makale Özeti

Visual Studio ile gelen yenilikleri incelemeye devam ediyoruz...

Makale

Visual Studio 2005 (2), C# ve Generics (Genel Türler)

Visual Studio 2005 (2), Generics (Genel Türler)

Visual Studio 2005’i incelemeye devam ediyoruz. Bu yazımızın konusu .Net Framework 1.2 ile ortaya çıkan Genel Türler (Generics) kavramı.

Genel türler, gerçek veritipleri kullanılmadan, her tür veri tipi ile çalışabilecek yapılar oluşturulabilmesini sağlayan yeni bir CLR eklentisidir. Mevcut problemleri ele alırsak, genel türlerin nerelerde kullanılabileceği ve avantajları daha kolay anlaşılır olacaktır...

 

.Net sınıf kütüphanesi içinde sıkça kullandığımız Stack sınıfını ele alalım. Hem stack sınıfının nasıl çalıştığını gösterebilmek hem de örneği daha kolay anlaşılır kılmak için Stack sınıfının daha az yetenekli, daha hafif bir versiyonunu yazdım:

 

class Yigin {

 

      object[] liste;

 

      int ogeSayisi;

 

      public Yigin() {

            liste = new object[1];

            ogeSayisi = 0;

      }

 

      public void Ekle(object deger) {

            liste[ogeSayisi++] = deger;

 

            if (ogeSayisi >= liste.Length) {

                  object[] geciciDizi = new object[liste.Length * 2];

 

                  liste.CopyTo(geciciDizi, 0);

                  liste = geciciDizi;

            }

      }

 

      public object Cikar() {

            object deger = liste[--ogeSayisi];

            return deger;

      }

}

 

Yazdığım Yigin sınıfının sadece iki metodu var. Yığına ekleme yapmak için Ekle(object), ve yığından çıkartma yapmak için Cikar() metodu. Yığınların çalışma şekli son giren ilk çıkar mantığına dayanmaktadır. Yani yığına eklediğim nesneler üst üste dizilir ve ben hep en üstten, en son eklediğim nesneye ulaşabilirim. Aşağıdaki kod parçası ile bir Yigin nesnesi oluşturup kullanıyorum.

 

Yigin yigin = new Yigin();

yigin.Ekle(25);

yigin.Ekle(165);

 

int sayi1 = (int) yigin.Cikar();

int sayi2 = (int) yigin.Cikar();

 

MessageBox.Show(sayi1.ToString());

MessageBox.Show(sayi2.ToString());

 

Yığın nesnesine önce iki int değişken ekliyorum. Dikkat edilmesi gereken nokta, ben bu iki int değişkeni yığına eklediğimde, değişkenler bir kutulama (boxing) işleminden geçiyor, çünkü Ekle metodu sadece object tipinde nesneler kabul ediyor. İlk performans problemini burada yaşamış oluyorum. Yığına eklediğim int değişkenlere daha sonra ulaşmak istediğimde, Yigin nesnesinin Cikar metodunu kullaniyorum. Cikar metodunun dönüş tipine bakarsanız, object olduğunu görürsünüz. Cikar metodu int değil, object döndürdüğü için, int değişkenlerime ulaşma amacıyla tip dönüşümü kullanarak Cikar metodunun döndürdüğü veriyi int’e dönüştürüyorum. İkinci performans kaybını da burada yaşıyorum. Eğer listeye eklediğim değişken int gibi bir değer tipi (value type) değil de referans tipi olsaydı (örneğin string vb..) Cikar metodunun döndürdüğü değeri yine tip dönüşümü ile string’e döndürmem gerekecekti. Yine performans kaybı... Performans kaybından öte, bir diğer problem de tip güvenliğini ihlal ediyor olmam. Aşağıdaki kod tamamen kurallar dahilinde, ancak çalışma zamanı hatası üretecek bir kod...

 

Yigin yigin = new Yigin();

yigin.Ekle(25);

yigin.Ekle(165);

 

int sayi1 = (int) yigin.Cikar();

string sayi2 = (string) yigin.Cikar();

 

Yigin nesnesine önce iki int değişken yüklüyorum. Fakat listeye attığım değişkenlerden ikincisini listeden çıkartırken değişkeni int yerine string’e dönüştürüyorum. Kod hatasız derleniyor ancak programı çalıştırdığımda InvalidCastException ile karşılaşıyorum.

 

Bu problemin üstesinden gelinebilir tabi ki, her veri tipi için kendi koleksiyonumuzu yazarak, örneğin Yigin sınıfının kodunu object ile değil, string ile ya da int ile çalışacak şekilde değiştirebiliriz. (Ya da tam bir tiplendirilmiş koleksiyon uygulaması için bkz. Tiplendirilmiş Koleksiyonlar Oluşturmak). Problemi kökten çözmüş oluyoruz ancak, kullandığımız her veri tipi için bir Yigin nesnesi kodlamak da beraberinde bir işkence problemini getiriyor. Tabi ki çözüm genel türler...

 

Bir genel tür tanımlamak için yapılan şey sadece veri tipi bilgisinin sınıf kodu içerisinden çıkarılması. Yani Yigin sınıfını ele alırsak, ilk yapılacak şey Yigin sınıfını veri tipi bağımsız hale getirmek. Bunu yapmak için, öncelikle Yigin sınıfının içinde sakladığı nesneleri tutan liste adındaki dizisini veritipsiz bir hale getirmek, Ekle metodunun sadece object türleri değil, her türde parametre almasını sağlamak ve son olarak Cikar metodunun object değil her türde nesne döndürmesini sağlamak gerekiyor. İşlem karışık gibi görünse de aslında çok kısa ve basit.

 

Öncelikle Yigin sinifini bir genel tür olarak tanımlıyorum:

 

class Yigin<Veritipi> {

}

 

Yigin sınıfının tanımında artık bir tür parametresi var. <VeriTipi> ifadesi Yigin sınıfının hangi veri tipi ile çalışacağını gösteren bir tür parametresi. Sınıfın değiştirilmiş, genel türe dönüştürülmüş halini aşağıda görüyorsunuz. Yapılan değişiklikleri kırmızı ile işaretledim.

 

class Yigin<Veritipi> {

      Veritipi[] liste;

 

      int ogeSayisi;

 

      public Yigin() {

            liste = new Veritipi[1];

            ogeSayisi = 0;

      }

 

      public void Ekle(Veritipi deger) {

            liste[ogeSayisi++] = deger;

 

            if (ogeSayisi >= liste.Length) {

                  Veritipi[] geciciDizi = new Veritipi[liste.Length * 2];

 

                  liste.CopyTo(geciciDizi, 0);

                  liste = geciciDizi;

            }

      }

 

      public Veritipi Cikar() {

            Veritipi deger = liste[--ogeSayisi];

            return deger;

      }

}

 

Yigin sınıfının ilk hali ile karşılaştırdığınızda, object ifadelerinin yerine Veritipi ifadelerinin geldiğini fark edeceksiniz. Bunun anlamı şu: Her yeni Yigin nesnesi oluşturduğumda, nesnenin hangi veri tipi ile çalışacağını belirleyeceğim ve nesne Ekle ve Cikar metodlarında artık sadece belirlemiş olduğum veri tipi ile çalışacak. Kod ile gösterirsek:

 

Yigin<int> intYigin = new Yigin<int>();

 

Yukarıdaki kod ile sadece int değişkenlerle çalışacak bir Yigin nesnesi oluşturmuş oluyorum. <Veritipi> tür parametresinin yerine <int> yazarak, Yigin sınıfının kodunda bir üst kod parçasında kırmızı ile işaretlediğim her ifadeyi sanki int ile değiştirmişim gibi, Yigin sınıfı int ile çalışmaya başlıyor. Ufak bir örnek aşağıda:

 

Yigin<string> strYigin = new Yigin<string>();

strYigin.Ekle("Selam ");

strYigin.Ekle("Dünya ");

MessageBox.Show(strYigin.Cikar() + strYigin.Cikar());

 

Yeni bir Yigin nesnesi oluşturdum ancak bu sefer tür parametresi olarak <string> belirledim. Yani bundan sonra Ekle ve Cikar metodları yalnız string türünde değişkenlerle çalışacak. strYigin.Ekle metodu ile yığın nesnesine “Selam” ve “Dünya” stringlerini ekliyorum. Daha sonra mesaj kutusu ile yığından sırayla çıkarttığım bu değerleri birleştirerek kullanıcıya gösteriyorum. Bu arada mesaj “Selam Dünya “ değil, “Dünya Selam “ şeklinde görünüyor. Yığın nesnesine en son eklenen en önce çıkıyordu hatırlarsanız.

 

Genel türlere destek sağlayan yapı ise başta da söylediğim gibi Common Language Runtime. MSIL diline eklenen yeni birtakım özelliklerle genel türlerin kullanımı mümkün kılınmış oluyor. Bu noktada şunu da eklemeliyim; genel türlerin desteği MSIL seviyesinde sağlandığına göre .Net dillerinin tümü genel türlerden faydalanabiliyor. Örneklerde hep C# üzerinden ilerledik, devam eden yazılarımda VB.Net ile de genel türlerin kullanımına bakacağız.

 

Genel türler .Net framework’e eklenen güçlü özelliklerden bir tanesi. Sonraki yazımda genel arayüzler (generic interfaces), genel metodlar (generic methods), genel tür kısıtlamaları (constraints), genel tür delegeleri (generic delegates), genel tür kuralları (generic rules), anonim metodlar (anonymous methods), iterators ve kısmi tipler (partial types) konularından bahsedeceğim...

 

Önsel Akın

onsela@yazgelistir.com