Makale Özeti

Bu makalede C# 2.0 ile gündeme gelen Generic sınıflar konusu ve türden bağımsız çalışan algoritmaların anlatılmaktadır.

Makale

C# 2.0 ve Generic Sınıflar -1

Giriş

    Yazılım mühendisliğinde türden bağımsız işlemler yapabilme amacı ile geliştirilen tekniklerin çoğu zaman performansa olan yansımaları olumsuzdur. Sözgelimi türden bağımsız sıralama işlemleri için kullanılan QuickSort isimli algoritma, içsel çalışma biçimi nedeni ile türe özgü sıralama işlemleri yapan diğer algoritmalara izafeten yavaş çalışmaktadır.

    Nesne yönelimli programlama dillerinin evrim sürecinde ise, türden bağımsız işlemler yapabilme çabasının bir sonucu olan ve birbirine benzer yaklaşımlara dayalı çözümler geliştirilmiştir. Örneğin Eiffel ve ADA dillerindeki genericler ya da C++ ta ki templateler bu yaklaşıma örnek teşkil eder niteliktedir. 

    C# dilinin tasarımında ise (2.0 a kadar) sintaksa, türden bağımsız çalışacak veri yapıları ya da algoritmaların üretimine yönelik bir tekniğin entegre edilmediği gözlenmektedir. Bu noktada söz konusu işlemlerin gerçekleştirilmesi, tümüyle .NET Framework sınıf sisteminin iç dinamiklerine bırakılmıştır.

C# İle Türden Bağımsız Veri Yapıları & Algoritmaların Tasarımı

    Yukarıda da söylendiği gibi C# ı kullanarak, türden bağımsız işlemler gerçekleştirebilecek yapılar tasarlamayı amaçlayan bir programcının tek seçeneği; .NET Framework sınıf sisteminin, "mutlak taban sınıfı" konumunda olan "System.Object" sınıfını (ve dolayısı ile ifade ettiği türü) kullanmaktır.  

    Bilindiği gibi Object sınıfı, .NET Framewok sınıf sistemine ilişkin türetme hiyerarşisinin tepesinde bulunmakta ve bu özelliği sayesinde de kendisinden türetme (inheritance) yolu ile elde edilmiş olan diğer tüm türlerle, "tür uyuşumu" sağlayabilmektedir. Programcı gerçekleştireceği tasarımda Object sınıfını ve sözü edilen tür uyuşum özelliğini kullanarak, türden bağımsız yapıları kolaylıkla tasarlayabilir.

Örnek : Aşağıdaki kod parçasında Object sınıfı kullanılarak yazılmış bir stack sistemi örneklenmektedir.

class CStack
{
    private object[] items;
    // Stackteki elemanların tutulacağı dizi

    private int size;
    // Eleman sayısı

    public CStack()
    { // Constructor
        items = new object[10];
        size = 0; 
    }

    public void Push(object item)
    { // Stacke eleman ekleyen fonksiyon
        if (size >= items.Length)
        {
            object[] tmp = new object[size * 2];
            Array.Copy(items, tmp, size); items = tmp;
        }
        items[size++] = item; 
    }

    public object Pop()
    { // Stackten eleman alan fonksiyon
         return (items[--size]);
    }
}

class Program
{
    static void Main(string[] args)
    {
         CStack stk = new CStack();
        
stk.Push("Deniz6");
         Console.WriteLine(stk.Pop()); 
    }
}

Tasarımın Analizi :

Örnekteki tasarım test edildiğinde herhangi bir anomali olmaksızın çalışacaktır. Ancak Object sınıfının tür uyuşum özelliğine dayalı bu yapının bir takım dezavantajları olduğu bilinmelidir. Zira bu makalede hedeflenen olgu; bu özelliğin kullanımını anlatmak değil, odağında bu özelliğin yer aldığı tasarımların neden olduğu komplikasyonları vurgulamak ve tasarımcıya alternatif hareket tarzlarını önermektir !  

Bu noktadan itibaren türden bağımsız çalışma mekanizması, Object sınıfının tür uyuşum yeteneğine bağlı olarak tasarlanan CStack isimli sınıfın, belirli durumlarda neden olduğu komplikasyonlar incelenecektir : 

1) Bilinçli tür dönüşümleri :  CStack sınıfı kullanılarak oluşturulan bir stack sisteminde, stacke referans türdeki bir nesnenin eklenmesi herhangi bir "boxing işlemine" neden olmamakla birlikte, Pop() metodu kullanılarak, stackten eleman alınması gerektiği durumlarda, bilinçli tür dönüşümünün yapılması kaçınılmaz olmaktadır. Bu durum hem kod yazımını sıkıcı hale getirmekte hem de çalışma zamanında gerçekleşen "tür uyuşum sınamaları" nedeni ile performansı olumsuz etkilemektedir.  

class CStack {
    // ...
}


class Program

    class Submarine {
        private string name;
        private string no;
        private int disp;

        public Submarine(string nm, string no, int dsp)
        {
            this.name = nm;
            this.no = no; 
            this.disp = dsp;
        } 
    }
 
    static void Main(string[] args)
    { 
        CStack stk = new CStack();

        stk.Push(new Submarine("Anafartalar", "S-356", 1500));

        Submarine sub = (Submarine) stk.Pop();
    }
}

2) Boxing & Unboxing İşlemleri : Stacke eklenen elemanın türü bir "değer tür" ise; Push() fonksiyonuna yapılan parametre aktarımı sırasında bir boxing işlemi gerçekleşmektedir. Keza benzer şekilde, stackten eleman alınması noktasında Pop() fonksiyonunun dönüş değerinin türü object olduğu için hem bilinçli tür dönüşümü gerekmekte hem de otomatik olarak unboxing işlemi gerçekleşmektedir. Sözü edilen boxing ve unboxing işlemleri, arka planda dinamik bellek tahsisatlarının ve "çalışma zamanı tür uyuşum sınamalarının" yapılmasına neden olmaktadır. Sayılan tüm bu otomatik işlemlerin performans üzerindeki etkileri olumsuzdur.

static void Main(string[] args) 

    CStack stk = new CStack(); 

    stk.Push(150); // Boxing

    int i = (int) stk.Pop();  // Unboxing 
}

3) Tür : Yukarıda örneklenen tasarımın bir başka dezavantajı ise; sınıfın kullanımı sırasında kesin olarak bir tür belirlemesinin yapılamayışıdır. Daha somut bir ifade ile, CStack sınıfı kullanılarak yaratılan bir stack sistemine, farklı türdeki bir veri eklenip, daha sonra aynı veri ilgisiz bir türe dönüştürülerek stackten alınabilir. Örneğin aşağıdaki gibi bir kod, problemsiz bir şekilde derlenecektir.

static void Main(string[] args)
{
    CStack stk = new CStack();

    stk.Push(new Submarine("Anafartalar", "S-356", 1500));

    string sub = (string) stk.Pop();
}

Ancak yukarıda yapılan tür dönüşümü, çalışma zamanında bir hataya (exceptiona) neden olacaktır. (Invalid cast exception : Specified cast is not valid)

Generic Sınıflar

C# 2.0 ile birlikte gündeme gelen "Generic Sınıflar" kullanılarak, yukarıda sayılan sorunlar yaşanmaksızın, türden bağımsız işlemleri gerçekleştirebilecek yapılar tasarlanabilir. Generic sınıflar, -aralarında bazı nüanslar olmak kaydı ile- C++ programlama dilinde var olan templatelere benzetilebilir. 

Generic Sınıfların Bildirimi & Kullanılması

Generic sınıfların bildiriminde sınıf isminden sonra < > işaretleri arasında tür parametresi belirtilir. Generic bir sınıf bildiriminin genel biçimi şöyledir :

class Sınıf_İsmi <Tür_Parametresi>
{
    // ...
}

Tür parametresi; bildirim sırasında herhangi bir türün karşılığı olmayan ve çoğu zaman T, G, AD gibi bir yer tutucudur. Bu yer tutucu(lar) sınıfın kullanımı noktasında bir tür belirtilmesi ile anlam kazanır(lar). Örneğin;

class Sample <T>
{
    // ...


// ....

public static void Main()
{
    Sample <int> obj = new Sample <int> ();
}

Yukarıdaki örnekte bildirim sırasında tür parametresi olarak kullanılan <T>, sınıfın kullanımı noktasında <int> türünün belirtilmesi ile anlam kazanmıştır.

NOT : Terminolojide Sample <int> gibi tanımlanan türler; constructed type biçiminde adlandırılmaktadır !  

Çok tipli generic sınıfların bildiriminde birden fazla tür parametresi kullanılabilir, örneğin :

class Sample <AB, CD>
{
    // ...


// ....

public static void Main()
{
    Sample <int, string> obj = new Sample <int, string> ();
}

Bu örnekte ise; AB -> int , CD -> string türlerinin karşılığı olmuşlardır.

Örnek : Aşağıdaki uygulamada CStack sınıfı, türden bağımsız çalışmayı sağlamak üzere generic bir sınıf olarak tasarlanmıştır.

class CStack <T>
{
   
private T[] items;
    // Stackteki elemanlarin tutulacagi dizi

    private int size;
    // Eleman sayisi

    public CStack()
    {
        items = new T[10];
        size = 0;
    }

    public void Push(T item)
    {
         if (size >= items.Length) {
              T[] tmp = new T[size * 2];
              Array.Copy(items, tmp, size);
              items = tmp;
         }
         items[size++] = item;
    }

     public T Pop()
     {
         return (items[--size]);
     }
}

class Program
{
     static void Main(string[] args)
     {
          CStack <string> stk = new CStack <string> ();
          stk.Push("Deniz6");
         
Console.WriteLine(stk.Pop());
     }
}

Konuya giriş niteliğindeki bu makale, türden bağımsız çalışma ve generic sınıflara ilişkin temel bilgileri içermektedir. Konunun detayları ve generic sınıflar ile C++ ta ki templatelerin arasındaki farklar bu yazının devamı niteliğindeki makalelerde anlatılacaktır. 

Referanslar :
Hejlsberg.book chp:19
.NET Framework SDK 2.0


Aykut TAŞDELEN

MVP (MS Most Valuable Professional)

aykutt@yazgelistir.com