C# Generic Liste-Sınıf Yapıları

Soldaki kupa fotografını generic konusunu araştırırken buldum çok hoşuma gittiği için sizle paylaşmak istedim. Aslında fotograf tüm konuyu özetliyor T kupamızın içerisine koyacağımız çay, kahve, su gibi değişkenleri simgeliyor. Bizde T'ye bunlardan birini verip kupamızı dolduruyoruz. Yazının soununda fotografa dönün sizde çok seveceksiniz:)

Asıl konumuza dönersek neden generic sınıf ve listelere ihtiyaç duyuyoruz bunu anlamak için önce non-generic üyeler kullanarak bir kaç örnek yapalım bakalım ne takım problemler ortaya çıkacak görelim.

Önce hepimizin defalarca kullandığı dizileri düşünelim: Dizi tanımı yapılırken dizi hangi tipte değerler taşıyacak ise bunu belirtmemiz gerekiyor; dizimiz string tipinde değerler tutacak ise string, int tipinde değerler tutacaksa int tipinde dizi oluşturulur. Daha sonrada dizimize boyut vermemiz gerekir. Bunları örnek bir string dizisi tanımlayarak gösterelim:

static void Main(string[] args)
{
   // string tipinde 3 elemanli bir dizi olusturalim
   string[] strArray = {"Ahmet", "Mehmet", "Ali" };
}

 Gördüğünüz gibi dizimizin boyutunu 3 verdik bu yüzden bu dizi artık 3 değer tutar 4. değeri artık bu diziye atayamayız. Atarsak hata alırız. Peki bizim ne kadarlık bir diziye ihtiyaç duyduğumuz önceden belli değilse ne olacak? Ya çok büyük bir dizi tanımlayacağız ama bu da belekte boşa yer kaplayacaktır ya da tahmin ettiğimizden daha fazla kayıt gelince programımız patlayacaktır.

Bu durumu ortadan kaldırmak için System.Collection namespace'i altında bulunan ArrayList gibi collection sınıflarını kullanabiliriz. Bu collection'lara önceden boyut belirtmek zorunda değiliz. Add metodu ile istediğimiz sayıda obje saklayabiliriz.

static void Main(string[] args)
{
    ArrayList strArray = new ArrayList();
    strArray.Add("Ahmet");
    strArray.Add("Mehmet");
    strArray.Add("Ali");
    strArray.Add("Ayşe");
}

Gördüğünüz gibi bir boyut vermeden istediğimiz sayıda obje gönderebildik. Peki aynı ArrayList'e tüm gönderdiğimiz değerler string tipinde olmasına rağmen 5. obje olarak int tipinde bir obje gönderebilir miyiz?
  strArray.Add(1);
İşte aslında neden generic listelere ihtiyaç duyuyoruz bu sorunun cevabında gizli. Evet gönderebiliriz. Çünkü ArrayList object tipinde nesneler tutar. string, int, class tüm bunlar .net dünyasında bir object olduğu için ArrayList'te hepsi aynı anda tutulabilir. Peki bunun dezavantajları nelerdir? Listeleyelim:

* ArrayList'e bir eleman eklediğimizde bu eleman object tipinde değilse ArrayList object tipinde elemanlar tuttuğu için arkaplanda önce bu eleman objeye çevrilir öyle ArayList'e eklenir. Bu performans sorununa neden olacaktır. 
* Tersi içinde aynı durum söz konusudur. ArrayList'ten bir eleman çektiğimizde object tipinde bize dönecektir. Fakat biz bu elamanı int tipinde istiyorsak bunu int'e çevirmemiz gerekecektir. Aynı şekilde buda performans sorununa neden olacaktır. 
* foreach ile ArrayList'imiz üzerinde döndüğümüzü düşünelim bize gelen her nesne object olduğu için gerçek halinin ne olduğunu bilemeyiz. Gelen her elemanı int tipine çevirsek belki arada bir string tipinde "Ayşe" gelecektir bu durumda programımız hata alacaktır. Yani non-generic koleksiyonlar type-safe değildir.

Peki bu tip durumlardan kurtulmak için ne kullanmamız gerekiyor: Generic List<T>

// Bu List<T> artık sadece int tipinde elemanlar tutar
List intListesi = new List();
intListesi.Add(10);
intListesi.Add(2);

Bu durumda artık listeye int tipinde bir eleman eklenirken arka planda obje tipine çevrilmesine gerek kalmadı. Aynı şekilde generic listemizden bir eleman çektiğimizde bunun int tipinde olacağından eminiz, fazladan bir convert işlemi yapmamızda gerek kalmadı. Ayrıca dönecek elemanın int tipinde olacağı kesin olduğu için type-safe bir yapıya sahiptir.

Bunun gibi List<T>Stack<T>Queue<T>SortedSet<T> gibi generic liste yapıları mevcuttur bu yapılarla ilgili örnekleri ilerleyen yazılarımızda paylaşacağız. Ayrıca ilginç bir collection yapısı olan ObservableCollection<T>'da mevcuttur incelemenizi öneririm.

Bunların dışında .Net Freamwork'ü içerisinde bir çok generic interface, sınıf , metot ve liste bulunmaktadır. Bizde kendi generic interfacesınıf ve metodlarımızı yazabiliriz. Örnek bir metot yazarak bunu açıklayalım:

//int tipinde Swap
static void Swap(ref int a, ref int b)
{
   int temp;
   temp = a;
   a = b;
   b = temp;
}

Görüldüğü gibi gönderilen 2 int değerini birbirine atayan basit bir Swap metodu yazdık. Fakat düşünün ki daha sonra string tipindeki 2 elemanı birbirine atayacak bir metoda daha ihtiyaç duyduk. Bu durumda bir metot daha yazmamız gerekecek:

//string tipinde Swap
static void Swap(ref string a, ref string b)
{
   string temp;
   temp = a;
   a = b;
   b = temp;
}

Bunun gibi oluşturduğumuz Person sınıfındaki 2 nesneyi birbirine atamamız gerekebilir. Bu tip durumlarda sınıfımıza hep yeni bir metot yazmak zorunda kalıcaz. Bunun yerine generic bir metot yazabiliriz:

static void Swap<T>(ref T a, ref T b)
{
   T temp;
   temp = a;
   a = b;
   b = temp;
}

Şu şekilde artık tek metot üzerinden Swap işlemimizi yapabiliriz:

//int ile Swap
int a = 1;
int b = 2;
Swap<int>(ref a, ref b);

//string ile Swap
string s1 = "Ahmet";
string s2 = "Mehmet";
Swap<string>(ref s1, ref s2);

Şimdi birde generic bir sınıf tasarlayalım:

        public class Point<T>
        {
            // noktamizin x,y koordinatlari
            private T xPos;
            private T yPos;
            // constructor.
            public Point(T xVal, T yVal)
            {
                xPos = xVal;
                yPos = yVal;
            }
            // Generic properties.
            public T X
            {
                get { return xPos; }
                set { xPos = value; }
            }
            public T Y
            {
                get { return yPos; }
                set { yPos = value; }
            }
            public void ResetPoint()
            {
                //default T tipinin default degerini dondurur
                //T int olursa 0, string olursa null gibi
                X = default(T);
                Y = default(T);
            }
        }

Gördüğünüz gibi Point sınıfı tanımladık ve bu Point sınıfının içerisine ise X,Y koordinatları tanımladık. Fakat generic bir sınıf tanımı yaptığımız için bu koordinatların tipini önceden tanımlamadık. Bu sınıfı kullanan client isterse int tipinden Point nesnesi oluşuturur bu durumda X,Y kordinatları int tipinden olur; isterse de double tipinden oluşturur bu durumda da X,Y koordinatları double tipinden olur.

    // int ile Point
    Point<int> p = new Point<int>(10, 10);
    // double ile Point
    Point<double> p2 = new Point<double>(5.4, 3.3);

Burada default keywordu dikkatinizi çekmiş olabilir: default keyword'u kod commenti olarak yazdığımız gibi tipin default değerini döndürür. int için 0, string için null gibi.

where T : <ifade> Kullanımı:

Generic yapısını kurarken T için bazı kısıtlamalar koyabiliriz. Bunun için "where T : <ifade>" yapısı kullanılır:

where T : struct  = T'nin System.ValueType tipinde olması gerektiğini belirtmek için kullanılır.
where T : class   = T'nin System.ValueType tipinde olmaması gerektiğini belirtmek için kullanılır.
where T : new()  = T'nin default constructor'ının olması gerektiğini belirtmek için kullanılır.
where T : NameOfBaseClass = NameOfBaseClass yerine bir sınıf ismi yazılır ve T'nin bundan türemiş olması gerektiğini belirtmek için kullanılır.
where T : NameOfInterface = NameOfInterface yerine interface ismi yazılır ve T'nin bu interface'i veya interfaceleri implemente etmiş olması gerekir. Birden fazla interface yazılabilir.
Örnek kullanımına bakarsak:

public class MyGenericClass<T> where T : class, IDrawable, new()

Umarım yararlı olmuştur. Görüşmek üzere.

3 yorum :

  1. süper kardeşim eline sağlık

    YanıtlaSil
  2. Vay be, sağlam yazı olmuş, oturup dusunmek gerekli...

    YanıtlaSil
  3. Kardeşim ellerine sağlık. Çok faydalı, yararlı oldu benim için.

    YanıtlaSil