Bu makalemizde ,NET
Framework 2,0’la beraber C# diline ve CLR’ye eklenmiş olan generics
(genellemeler) lerden bahsetmek istiyorum. Genellemeleri kullanarak parametre
olarak farklı tipleri kabul edebilen ve bunlarla çalışabilen metotlar, sınıflar
yazabiliriz. C#’daki genellemeler C++’da yer alan templates (şablonlar)
konusuyla benzerlikler gösteriyor. Genellemelere C++ şablonlarının C# uyarlanmış
kullanışlı, güvenli yapılarak olarak bakabiliriz. C#’daki genellemelerle
C++’daki şablonlar arasında çeşitli benzerlikler ve farklar var. İleriki
yazılarda bu farklara değineceğiz.
Genellemelere
Giriş:
C#’da
genellemeleri kullanabilmemiz için System.Collections.Generic isim aralığını
kodumuza eklememiz gerekir. Bu isim aralığını ekledikten sonra kendi genel
sınıflarımızı (generic class), genel metodlarımızı (generic metod), genel
arayüzlerimizi (generic interface) ve genel temsilcilerimizi (generic delegate)
yazabiliriz. Ayrıca bu isim aralığında .NET kütüphanesi içerisine dahil edilmiş
olan çok sayıda hazır genel yapı bulunmaktadır. Aşağıdaki tabloda bu yapıların
tarifleri ve özellikleri sıralanmıştır. Bu sınıfların kullanım örneklerine
ileride değineceğiz.
Genel Sınıf veya Arayüz |
Açıklama |
Genellemeye Karşılık Olan Genel Olmayan Tip |
Collection<T>
ICollection<T> |
Genel bir koleksiyonun
temel sınıfı. |
CollectionBase
ICollection |
Comparer<T>
IComparer<T>
IComparable<T> |
Aynı genel tipte olan iki
nesneyi sıralamak ve eşitliğini kontrol etmek için kullanılan
yapılar. |
Comparer
IComparer
IComparable |
Dictionary<K, V>
IDictionary<K,V> |
Anahtar/değer ikilisi ile
temsil edilen anahtara göre organize edilmiş koleksiyonlar. |
Hashtable
IDictionary |
Dictionary<K,
V>.KeyCollection |
Dictionary<K, V> ’de yer
alan anahtar koleksiyonunu temsil eder. |
None. |
Dictionary<K,
V>.ValueCollection |
Dictionary<K, V> ’de yer
alan değer koleksiyonunu temsil eder. |
None. |
IEnumerable<T>
IEnumerator<T> |
foreach
ile taranabilen koleksiyonlar. |
IEnumerable
IEnumerator |
KeyedCollection<T, U> |
Anahtarlanmış
koleksiyonları temsil eder. |
KeyedCollection |
LinkedList<T> |
Bağlı listeleri temsil
eder. |
None. |
LinkedListNode<T> |
Bağlı listedeki bir dalı
temsil eder. |
None. |
List<T>
IList<T> |
IList<T> arayüzüne uyan
boyutu dinamik olarak arttırılabilen listeler. |
ArrayList
IList |
Queue<T> |
FIFO (ilk giren, ilk
çıkar)’ya uygun koleksiyonlar. |
Queue |
ReadOnlyCollection<T> |
Sadece okunabilir
koleksiyonlar için genel temel sınıf. |
ReadOnlyCollectionBase |
SortedDictionary<K, V> |
IComparer<T> arayüzüne
uygun olarak sıralanmış anahtar/değer ikililerinde oluşmuş bir
koleksiyonu temsil eder. |
SortedList |
Stack<T> |
LIFO (Son giren, ilk
çıkar)’ya uygun koleksiyonlar. |
Stack |
Genellemelerin
Kullanım Amacı:
Buraya kadar yazılanlar belki bize biraz anlamsız görülebilir. Genellemelerin bize sağladığı
faydaları anlamak için bir örnek üzerinden yola çıkacağız. Örneğin verilen bir
dizide istenilen elemanı arayan bir fonksiyon yazmak istiyoruz. Bunu şöyle
yazabiliriz:
using
System;
using
System.Collections.Generic;
using
System.Text;
namespace
generics_search
{
class Program
{
static void
Main(string[] args)
{
int[] sayilar ={ 1, 5, 6, 8 };
int ind;
ind = Ara(sayilar, 6);
Console.WriteLine(ind);
Console.ReadLine();
}
static int
Ara(int[] pDizi,
int p)
{
for (int
i = 0; i < pDizi.Length; ++i)
{
if (pDizi[i] == p)
return i;
}
return -1;
}
}
}
|
Buraya kadar umarız
herşey iyi gitmiştir. Ara fonksiyonunun parametreleri int sayılardan
oluşan bir dizi ve bu dizi içerisinde aramak istediğiz elemanı temsil eden
int tipinde bir sayıdır. Bu fonksiyonu int tipinde elemanlardan
oluşan tüm tek boyutlu diziler için rahatlıkla kullanabiliriz. Fonksiyonunun
geri dönüş değeri int dir ve dizide aranan elemanın ilk olarak bulunduğu
indisi temsil eder.
Ancak ondalıklı
sayılardan oluşan bir dizi içerisinde arama yapmak istediğimizde Ara
fonksiyonumuz işlerliğini yitirecektir. Bunun sebebi parametrelerinin int
tipinde tanımlanmış olamsıdır. Bu sefer sorunu gidermek üzere yeni bir Ara
fonksiyonu yazabiliriz.
static
int Ara(double[]
pDizi, double p) |
İmzasına
sahip Ara fonsiyonunun aşırı yüklenmiş (overloading) bir versiyonunu
yazabiliriz. Ancak ileride bir string dizisinin içerisinde arama yapmak
istersek yeni bir Ara versiyonu yazmamız gerekir.
using
System;
using
System.Collections.Generic;
using
System.Text;
namespace
generics_search
{
class Search
{
static
void Main(string[]
args)
{
int[] sayilar ={ 1, 5, 6, 8 };
string[] adlar ={
"ali",
"veli", "C#"};
int ind;
ind = Ara(sayilar, 8);
Console.WriteLine(ind);
ind = Ara(adlar, "C#");
Console.WriteLine(ind);
Console.ReadLine();
}
static int
Ara(int[] pDizi,
int p)
{
for (int
i = 0; i < pDizi.Length; ++i)
{
if (pDizi[i].Equals(p))
return i;
}
return -1;
}
static int
Ara(string[] pDizi,
string p)
{
for (int
i = 0; i < pDizi.Length; ++i)
{
if (pDizi[i].Equals(p))
return i;
}
return -1;
}
}
}
|
pDizi[i].Equals(p)
kullanarak, == operatörünün acaba iki string tipindeki değişkeni
karşılaştırmak için .NET kütüphanesinde overloaded edilip edilmediği sorusunu
kafamıza sormadık. Esasında == operatörü int, string ve diğer
ilkel türler için karşılaştırma yapmak amacı ile .NET kütüphanesinde aşırı
yüklenmiştir.
İşte genellemlerin
amacı tipten bağımsız olarak method, sınıf, arayüz, temsilci yazmaktır. Ara
fonsksiyonunun her değişken türü için ayrı ayrı yazılıp aşırı yüklenmesi veya
Ara2, StringAra, DoubleAra adında farklı birçok fonksiyon yazılması genel bir
metod kullanılarak önlenebilir.
Genel Fonksiyonlar
(Generic Methods):
Genel fonksiyonlar
birden fazla değişken tipine cevap verecek şekilde yazılan fonksiyonlardır.
void Yerdegistir<T>( ref T lhs, ref T rhs){ T temp; temp = lhs; lhs = rhs; rhs = temp;} |
Yukarıdaki örnekte
bir yerdeğiştirme fonksiyonu genel olarak tanımlanmıştır. Klasik fonksiyon
tanımlamalarından farklı olarak fonksiyonunun isminin sonun <T> eklenmiştir.
Burada T, tip parametresi (type parameter) olarak adlandırılır. T olarak
adlandırılması zorunlu değildir. Ancak standart olarak T kabul edilmiştir.
Fonksiyonun
çağırılması şöyle gerçekleştirilir.
int a = 1;int b = 2;Yerdegistir<int>(a, b); |
Olarak
çağırmak da mümkündür. Derleyici fonksiyon çağırılırken aktarılan parametrelerin
tipine bakarak uygun formattaki fonksiyonu kendi bulabilir.
Yerdegistir<int>(a, b); Yerdegistir<string>(a, b);
Yerdegistir<double>(a, b); |
Fonksiyonları
da eğer a ve b uygun tiplerde verilirse çağırılabilir. Ara genel fonksiyonumuz
şöyle yazılıp çağırılabilir.
using
System;
using
System.Collections.Generic;
using
System.Text;
namespace
generics_search
{
class Search
{
static void
Main(string[] args)
{
int[] sayilar ={ 1, 5, 6, 8 };
string[] adlar ={
"ali",
"veli", "C#" };
int ind;
ind = Ara<int>(sayilar, 8);
//ind = Ara(sayilar, 8); dogru
Console.WriteLine(ind);
ind = Ara<string>(adlar,
"C#");
//ind = Ara(adlar, "veli"); dogru
Console.WriteLine(ind);
Console.ReadLine();
}
static int
Ara<T>(T[] pDizi, T p)
{
for (int
i = 0; i < pDizi.Length; ++i)
{
if (pDizi[i].Equals(p))
return i;
}
return -1;
}
}
}
|
static
int Ara<T>(T[] pDizi, T p) |
Satırında farklı dizi tiplerinin kabul edilmesi için T[] parametre tipi olarak
kullanılmıştır.
Genel fonksiyonları kullanarak tip dönüştürmelerinden de bir miktar uzaklaşabiliriz. Örneğin bir
fonksiyon içerisinde bir tip dönüştürülmesinin yapılması başta performansı
etkilemeyebilir. Fakat fonksiyon ufak dahi olsa bir döngü içerisinde çok sayıda
çağırılıyorsa tip dönüştürmesi performans düşüşüne neden olacaktır. Genel bir
fonksiyon ise tip dönüştürmelerinden bir nebze uzak yazılabilirse (herzaman
mümkün olmayabilir) performans açısından üstünlük sağlayacaktır.
Makale:
C# 2.0'da Genellemeler - 1 C#, Visual C# ve .NET Ferhat Nutku
|