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 generic'ler ya da C++ 'ta ki template'ler bu yaklaşıma örnek teşkil eder niteliktedir.
C# dilinin tasarımında ise (2.0 'a kadar) sintaks'a, 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;
// Stack'teki elemanların tutulacağı dizi
private int size;
// Eleman sayısı
public CStack()
{ // Constructor
items = new object[10];
size = 0;
}
public void Push(object
item)
{ // Stack'e 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()
{ // Stack'ten 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 komplikasyon'ları 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 komplikasyon'lar incelenecektir :
1) Bilinçli tür dönüşümleri : CStack sınıfı kullanılarak oluşturulan bir stack sisteminde, stack'e referans türdeki bir nesnenin eklenmesi herhangi bir "boxing işlemine" neden olmamakla birlikte, Pop() metodu kullanılarak, stack'ten 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 : Stack'e 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, stack'ten 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 stack'ten 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 (exception'a) 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üans'lar olmak kaydı ile- C++ programlama dilinde var olan template'lere 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>
// Stack'teki 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.Push("Deniz6");
}
}
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 template'lerin arasındaki
farklar bu yazının devamı niteliğindeki makalelerde anlatılacaktır.
Referanslar :
Hejlsberg.book chp:19
.NET Framework SDK 2.0
MVP (MS Most Valuable Professional)