Bildiğiniz gibi C# dili 2001 yılında Microsoft tarafından çıkarılan ve
nesne yönelimli programlama tekniğine %100 destek veren bir programlama
dilidir. C#, programcılara sunulduğundan beri bir çok programcının dikkatini
çekmiştir. Bu ilgide en önemli neden herhalde C# dilinin kendinden önce çıkarılmış
olan JAVA ve C++ dillerini örnek almasıdır. Evet C# modern çağın gerektirdiği
bütün yazılım bileşenlerini içermekle beraber eski programlama dillerinde
bulunan iyi özellikleri de yapısında barındırmaktadır. Microsoft ve C# dil
tasarımcıları her geçen gün yeni piyasa araştırmaları yaparak dile
katabilecekleri özellikleri tartışmaktadırlar. Bu amaçla C# dilinin tasarımcıları
yakın bir zaman içinde C# diline eklemeyi düşündükleri yeni özellikleri
bildirmişlerdir. Bu yazıda muhtemelen "VS.NET for Yukon(VS.NET Everett'ten
sonraki versiyon)" ile birlikte uygulamaya konulacak C# dilinin muhtemel
özelliklerini özetlemeye çalışacağım. Bu bildirinin tamamını C# topluluğunun
resmi sitesi olan www.csharp.net
adresinden okuyabilirsiniz.
C# diline yakın bir zamanda eklenilmesi düşünülen özellikler 4 ana başlık altında toplanmıştır. Bu özellikler temel olarak aşağıdaki gibidir.
1 - Generics (Soysal Türler)
2 - Iterators
3 - Anonymous Methods (İsimsiz-Anonim- Metotlar)
4 - Partial Types (Kısmi Türler)
Bu yazıda yukarıda başlıklar halinde verilen her bir konuyu ayrıntılı olarak inceleyip, programcıya ne gibi faydalar sağlayabileceğini ve programların performansına nasıl etki edeceğine değineceğim.
1 - Generics
Profesyonel programlamada, türden bağımsız algoritma geliştirme önemli bir tekniktir. Türden bağımsız algoritmalar geliştirici için büyük kolaylıklar sağlamaktadır. Söz gelimi iki int türden sayının toplanmasının sağlayan bir fonksiyonu yazdıktan sonra aynı işlemi iki double türden sayı için tekrarlamak zaman kaybına sebep olacaktır. C++ dilinde türden bağımsız algoritma kurabilmek için şablon(template) fonksiyonları ve şablon sınıfları kullanılmaktadır. C#, türden bağımsız algoritma geliştirmeye doğrudan destek vermiyor olsada dolaylı yollardan türden bağımsız işlemler yapabilmek mümkündür. Bu işlemler C#'ta "Her şey bir Object'tir" cümlesinin altında yatan gerçekle halledilmektedir. C#'ta herşeyin bir nesne olması ve her nesnenin ortak bir atasının olması ve bu atanın da Object sınıfı olması bu cümlenin altında yatan gerçektir. Dolayısıyla herhangi bir türe ait referansı Object referasnlarına ataybiliriz. Yani bir bakıma türden bağımsız bir işlem gerçekleştirmiş oluyoruz. Söze gelimi Object türünden bir parametre alan bir fonksiyonu dilediğimiz bir nesne referansı geçebiliriz. Temel(base) sınıfa ait referanslara türeyen(inherited) sınıf referanslarını ataybilmek nesne yönelimli programlama tekniğinin sunduğu bir imkandır.
C#'ta Object referanslarına istenilen türden referanslar atanabilir. Bu, büyük bir imkan gibi görünsede aslında bazı dezavantajlarıda beraberinde getiriyor. Çünkü çalışma zamanında Object türüne atanmış referanslar orjinal türe tekrar geri dönüştürülmektedir. Kısaca unboxing olarak bilinen bu işlem özellikle değer(value) ve referans(reference) türleri arasında yapıldığında önemsenecek büyüklükte bir performans kaybı meydana gelmektedir. Çünkü değer ve referans türleri belleğin farklı bölgelerinde saklanmaktadır. Bu durum boxing ve unboxing işlemlerinin çalışma zamanında farklı bellek bölgeleri arasında uzun sürebilecek veri transferlerine sebep olur. Bu tür bir performans kaybını bazı veri yapıları için önlemek için C# dil tasarımcıları Generics isimli bi kavramın dile eklenmesini öngörmüşlerdir. Bu sayede bazı veri yapılarında özellikle .NET sınıf küyüphanesindeki System.Collections isim alanında bulunan veri yapılarında epeyce performans kazancı elde edilecektir.
C# diline yakın bir zamanda eklenilmesi düşünülen özellikler 4 ana başlık altında toplanmıştır. Bu özellikler temel olarak aşağıdaki gibidir.
1 - Generics (Soysal Türler)
2 - Iterators
3 - Anonymous Methods (İsimsiz-Anonim- Metotlar)
4 - Partial Types (Kısmi Türler)
Bu yazıda yukarıda başlıklar halinde verilen her bir konuyu ayrıntılı olarak inceleyip, programcıya ne gibi faydalar sağlayabileceğini ve programların performansına nasıl etki edeceğine değineceğim.
1 - Generics
Profesyonel programlamada, türden bağımsız algoritma geliştirme önemli bir tekniktir. Türden bağımsız algoritmalar geliştirici için büyük kolaylıklar sağlamaktadır. Söz gelimi iki int türden sayının toplanmasının sağlayan bir fonksiyonu yazdıktan sonra aynı işlemi iki double türden sayı için tekrarlamak zaman kaybına sebep olacaktır. C++ dilinde türden bağımsız algoritma kurabilmek için şablon(template) fonksiyonları ve şablon sınıfları kullanılmaktadır. C#, türden bağımsız algoritma geliştirmeye doğrudan destek vermiyor olsada dolaylı yollardan türden bağımsız işlemler yapabilmek mümkündür. Bu işlemler C#'ta "Her şey bir Object'tir" cümlesinin altında yatan gerçekle halledilmektedir. C#'ta herşeyin bir nesne olması ve her nesnenin ortak bir atasının olması ve bu atanın da Object sınıfı olması bu cümlenin altında yatan gerçektir. Dolayısıyla herhangi bir türe ait referansı Object referasnlarına ataybiliriz. Yani bir bakıma türden bağımsız bir işlem gerçekleştirmiş oluyoruz. Söze gelimi Object türünden bir parametre alan bir fonksiyonu dilediğimiz bir nesne referansı geçebiliriz. Temel(base) sınıfa ait referanslara türeyen(inherited) sınıf referanslarını ataybilmek nesne yönelimli programlama tekniğinin sunduğu bir imkandır.
C#'ta Object referanslarına istenilen türden referanslar atanabilir. Bu, büyük bir imkan gibi görünsede aslında bazı dezavantajlarıda beraberinde getiriyor. Çünkü çalışma zamanında Object türüne atanmış referanslar orjinal türe tekrar geri dönüştürülmektedir. Kısaca unboxing olarak bilinen bu işlem özellikle değer(value) ve referans(reference) türleri arasında yapıldığında önemsenecek büyüklükte bir performans kaybı meydana gelmektedir. Çünkü değer ve referans türleri belleğin farklı bölgelerinde saklanmaktadır. Bu durum boxing ve unboxing işlemlerinin çalışma zamanında farklı bellek bölgeleri arasında uzun sürebilecek veri transferlerine sebep olur. Bu tür bir performans kaybını bazı veri yapıları için önlemek için C# dil tasarımcıları Generics isimli bi kavramın dile eklenmesini öngörmüşlerdir. Bu sayede bazı veri yapılarında özellikle .NET sınıf küyüphanesindeki System.Collections isim alanında bulunan veri yapılarında epeyce performans kazancı elde edilecektir.
İsterseniz basit bir yığın(stack) sınıfı üzerinden "generics"
kavramının sağlayacağı yaraları ve boxing/unboxing işlemlerinin etkisini
inceleyelim.
.NET sınıf kütüphanesinde de bulunan Stack sınıfı içinde her türden veri bulunduran ve istenildiğinde bu verilere LIFO(son giren ilk çıkar) algoritmasına göre veri çekilebilen bir veri yapısdır. .NET'teki Stack sınıfı ile bütün veri türlerine ait işlemleri yapabilmek için Stack sınıfındaki veri yapısı Object olarak seçilmiştir. Eğer bu böyle olmasaydı Stack sınıfının her bir tür için ayrı ayrı yazılması gerekecekti. Bu mümkün olsa bile herşey bitmiş olmayacaktı. Çünkü Stack sınıfı kullanıcını tanımlayacağı türleri barındıracak duruma gelmez. İşte bütün bu sebeplerden dolayı Stack veri yapısında saklanan veriler Object olarak seçilmiştir. Buna göre Stack sınıfının arayüzü aşağıdaki gibidir.
.NET sınıf kütüphanesinde de bulunan Stack sınıfı içinde her türden veri bulunduran ve istenildiğinde bu verilere LIFO(son giren ilk çıkar) algoritmasına göre veri çekilebilen bir veri yapısdır. .NET'teki Stack sınıfı ile bütün veri türlerine ait işlemleri yapabilmek için Stack sınıfındaki veri yapısı Object olarak seçilmiştir. Eğer bu böyle olmasaydı Stack sınıfının her bir tür için ayrı ayrı yazılması gerekecekti. Bu mümkün olsa bile herşey bitmiş olmayacaktı. Çünkü Stack sınıfı kullanıcını tanımlayacağı türleri barındıracak duruma gelmez. İşte bütün bu sebeplerden dolayı Stack veri yapısında saklanan veriler Object olarak seçilmiştir. Buna göre Stack sınıfının arayüzü aşağıdaki gibidir.
class Stack
{
private int current_index;
private object[] elemanlar = new object[100];
{
private int current_index;
private object[] elemanlar = new object[100];
public void Push(object
veri)
{
.
.
elemanlar[current_index] = veri;
.
.
}
public object Pop()
{
.
.
return elemanlar[current_index];
.
.
}
}
{
.
.
elemanlar[current_index] = veri;
.
.
}
public object Pop()
{
.
.
return elemanlar[current_index];
.
.
}
}
Bildirilen bu
Stack sınıfının elemanı Object türünden olduğu için Push() metodu ile istediğimiz
türden veriyi saklayabiliriz. Aynı şekilde Pop() metodu ile bir veri çekileceği
zaman veri Object türünden olacaktır. Pop() metodu ile elde edilen verinin
gerçek türü belli olmadığı için tür dönüştürme operatörü kullanılır. Örneğin,
Push(3);
şeklinde yığına
eklenen veriyi tekrar elde etmek için
int a = (int)Pop();
biçiminde bir tür dönüşümü yapmamız gerekir. Bu işlemler kendi tanımlayacağımız
özel sınıflar içinde geçerlidir. Ancak int ve double gibi temel veri
türlerindeki performans kaybı daha fazladır. Çünkü Push(3) şeklindeki bir çağrımda
boxing işlemi gerçekleşirken Pop() metodunun çağrılmasında unboxing işlemi
gerçekleşir. Üstelik bu durumda Pop() metodunun geri dönüş değerini byte türüne
dönüştürmeye çalışırsak derleme zamanında herhangi bir hata almayız. Bu da çalışma
zamanında haberimiz olmadan bazı veri kayıplarının olabileceğini gösterir.
Kullanıcı tanımlı sınıflar ilgili bir yığın kullanıyorsak Pop() metodunun geri
dönüş değerini farklı bir kullanıcı tanımlı sınıfa çaviriyorsak bu sefer de
derleme zamanında hata alınmaz, ancak çalışma zamanında "invalid cast
operation" istisnai durumu meydana gelir.
Bütün eksi durumlardan kurtulmak için generics(soysal tür)'lerden faydalanılabilir. Soysal türler C++ dilindeki şablon sınıflarının bildirimi ile benzerdir. Bu tür sınıf bildirimlerine parametreli tip de denilmektedir. Parametreli tipler aşağıdaki gibi bildirilir.
Bütün eksi durumlardan kurtulmak için generics(soysal tür)'lerden faydalanılabilir. Soysal türler C++ dilindeki şablon sınıflarının bildirimi ile benzerdir. Bu tür sınıf bildirimlerine parametreli tip de denilmektedir. Parametreli tipler aşağıdaki gibi bildirilir.
class Stack
{
private int current_index;
private Veri türü[] elemanlar;
{
private int current_index;
private Veri türü[] elemanlar;
public void Push(Veri türü veri)
{
.
.
elemanlar[current_index] = veri;
.
.
}
public Veri türü Pop()
{
.
.
return elemanlar[current_index];
.
.
}
}
{
.
.
elemanlar[current_index] = veri;
.
.
}
public Veri türü Pop()
{
.
.
return elemanlar[current_index];
.
.
}
}
ile stack sınıfnın hangi türden verileri tutacağı stack nesnesini oluşturacak
programcıya bırakılmıştır. Örneğin int türden verileri saklayacak bir yığın aşağıdaki
gibi oluşturulur.
Stack<int> yıgın = new Stack<int>;
Yukarıdaki şekilde bir yıgın oluştrulduğunda Stack sınıfınuın
bildirimindeki Veri türü ifadeleri int türü olarak ele alınacaktır. Dolayısıyla
Pop() metodu ile yığından bir eleman çıkarılıp aşağıdaki gibi başka bir değişkene
atanmak istendiğinde tür dönüştürme operatörünü kullanmaya gerek yoktur. Bu da
boxing ve unboxing işlemlerinin gerçekleşmediği anlamına gelir ki istediğimiz
de buydu zaten.
Stack<int> yıgın = new Stack<int>;
yıgın.Push(3); // Boxing işlemi gerçekleşmez.
int a = yıgın.Pop(); //Unboxing işlemi gerçekleşmez.
yıgın.Push(3); // Boxing işlemi gerçekleşmez.
int a = yıgın.Pop(); //Unboxing işlemi gerçekleşmez.
Aynı şekilde yığınımızın double türden verileri saklamasını istiyorsak
int yerine double kullanmalıyız. Bu durumda çalışma zamanında hem int hem de
double verileri tutan yığın sınıfları oluşturulacaktır. Biz tek bir yığın sınfı
bildirmiş olmamıza rağmen çalışma zamanı bizim için ayrı iki yığın sınıfı oluşturur.
Soysal türleri kendi tanımladığımız sınıflar içinde oluşturabiliriz. Örneğin Musteri isimli bir sınıfın verilerini yığında tutmak için yığın sınıfını aşağıdaki gibi oluşturmalıyız.
Soysal türleri kendi tanımladığımız sınıflar içinde oluşturabiliriz. Örneğin Musteri isimli bir sınıfın verilerini yığında tutmak için yığın sınıfını aşağıdaki gibi oluşturmalıyız.
Stack yıgın = new Stack;
Bu durumda yığına sadece Musteri nesneleri eklenebilir. Yani yıgın.Push(3)
şeklindeki bir kullanım derleme aşamasında hata verecektir. Aynı zamanda yığından
çekilecek veriler de Musteri türündendir. Dolayısıyla tür dönüşümü uygun türler
arasında olmalıdır.
Yığın sınıfı yukarıda anlatılan şekilde kullanıldığında yığındaki elemanların belirli bir türden olduğu garanti altına alınır. Böylece Musteri türünden nesneleri tutan bir yığına "3" gibi bir sayıyı ekleyemeyeceğimiz için daha gerçekçi programlar yazılır.
Stack örneğinde sadece bri tane parametre türü kullandık. Soysal türlerde istenilen sayıda parametreli tür kullanılabilir. Örneğin Hashtable sınıfnındaki Deger ve Anahtar ikilisi aşağıdaki gibi parametreli tür olarak bildirilebilir.
Yığın sınıfı yukarıda anlatılan şekilde kullanıldığında yığındaki elemanların belirli bir türden olduğu garanti altına alınır. Böylece Musteri türünden nesneleri tutan bir yığına "3" gibi bir sayıyı ekleyemeyeceğimiz için daha gerçekçi programlar yazılır.
Stack örneğinde sadece bri tane parametre türü kullandık. Soysal türlerde istenilen sayıda parametreli tür kullanılabilir. Örneğin Hashtable sınıfnındaki Deger ve Anahtar ikilisi aşağıdaki gibi parametreli tür olarak bildirilebilir.
public class Hashtable
{
public void Add(AnahtarTuru anahtar, DegerTuru deger)
{
.....
}
public DegerTuru this[AnahtarTuru anahtar]
{
.....
}
}
{
public void Add(AnahtarTuru anahtar, DegerTuru deger)
{
.....
}
public DegerTuru this[AnahtarTuru anahtar]
{
.....
}
}
Yani bir Hashtable nesnesi oluşturulacağı zaman her iki parametre türü
de belirtilmelidir. Örneğin Anahtar türü int olan ve değer türü Musteri sınıfı
olan bir Hashtable nesnesi aşağıdaki gibi oluşturulabilir.
Hashtable<int,Musteri> hashtable =
new Hashtable<int,Musteri>;
Not : Parametre sayısını aralarına virgül koyarak dilediğimiz kadar artırabiliriz.
Soysal türlerin saydığımız avantajlarının yanında bu haliyle bazı dezavantajları ve kısıtlamalarıda vardır. Söz gelimi Hashtable sınıfının bildirimi içinde AnahtarTuru verisinin bazı elemanlarını bir ifade de kullanmak istiyoruz; derleyici hangi AnahtarTuru parametrelei türünün hangi türden olduğunu bilmediği için bu durumda sadece Object sınıfının ait metotlar ve özellikler kullanılabilir. Mesela Hashtable sınıfının Add metodu içinde anahtar parametresi ile CompareTo() metodunu kullanmak istiyorsak CompareTo metodunun bildirildiği IComparable arayüzünü kullanarak aşağıdaki gibi tür dönüşümü yapmalıyız.
Soysal türlerin saydığımız avantajlarının yanında bu haliyle bazı dezavantajları ve kısıtlamalarıda vardır. Söz gelimi Hashtable sınıfının bildirimi içinde AnahtarTuru verisinin bazı elemanlarını bir ifade de kullanmak istiyoruz; derleyici hangi AnahtarTuru parametrelei türünün hangi türden olduğunu bilmediği için bu durumda sadece Object sınıfının ait metotlar ve özellikler kullanılabilir. Mesela Hashtable sınıfının Add metodu içinde anahtar parametresi ile CompareTo() metodunu kullanmak istiyorsak CompareTo metodunun bildirildiği IComparable arayüzünü kullanarak aşağıdaki gibi tür dönüşümü yapmalıyız.
public class Hashtable
{
public void Add(AnahtarTuru anahtar, DegerTuru deger)
{
switch(((IComparable)anahtar).CompareTo(x))
{
}
}
}
{
public void Add(AnahtarTuru anahtar, DegerTuru deger)
{
switch(((IComparable)anahtar).CompareTo(x))
{
}
}
}
Hashtable sınıfının Add() metodu yularıdaki şekilde bildirilse bile hala
eksik noktalar var. Mesela AnahtarTuru parametresi eğer gerçekten IComparable
arayüzünü uygulamıyorsa switch ifadesi içinde yapılan tür dönüşümü geçersiz
olacaktır ve çalışma zamanında hata oluşacaktır. Çalışma zamanında meydana
gelebilecek bu tür hataları önlemek için yapılabilecek tek şey AnahtarTuuru
parametresinin IComparable arayüzünü uyguluyor olmasını zorlamaktır. Bu işlemi
yapmak için AnahtarTuru parametresine çeşitli kısıtlar(constraints) getirilir.
Aşağıdaki Hashtable sınıfında AnahtarTuru parametresinin IComparable arayüzünü
uygulaması gerektiği söylenmektedir. Bu kısıt için where anahtar sözcüğü kullanılır.
public class Hashtable<AnahtarTuru, DegerTuru> where AnahtarTuru : IComparable
{
public void Add(AnahtarTuru anahtar, DegerTuru deger)
{
switch(anahtar.CompareTo(x))
{
}
}
}
{
public void Add(AnahtarTuru anahtar, DegerTuru deger)
{
switch(anahtar.CompareTo(x))
{
}
}
}
Dikkat ettiyseniz uygulanan kısıttan sonra switch ifadesi içinde anahtar
değişkeni üzerinde tür dönüşümü işlemi yapmaya gerek kalmamıştır. Üstelik
kaynak kodun herhangi bir noktasında Hashtable nesnesini IComparable arayüzünü
uygulamayan bir AnahtarTuru parametresi ile oluşturursak bu sefer ki hata
derleme zamanında oluşacaktır.
Not : parametreli türler üzerindeki kısıt sadece arayüz olmak zorunda değildir. Arayüz yerine sınıflar da kısıt olarak kullanılabilir.
Bir parametreli türe birden fazla arayüz kısıtı konabileceği gibi aynı sınıftaki diğer parametreleri türler için de kısıt konulabilir. Ancak bir parametreli tür için ancak sadece bir tane sınıf kısıt olabilir. Örneğin aşağıdaki Hashtable sınıfında DegerTuru Musteri sınıfından tremiş olması gerekirken, AnahtarTuru hem IComparable hemde IEnumerable arayüzünü uygulamış olması gerekir.
Not : parametreli türler üzerindeki kısıt sadece arayüz olmak zorunda değildir. Arayüz yerine sınıflar da kısıt olarak kullanılabilir.
Bir parametreli türe birden fazla arayüz kısıtı konabileceği gibi aynı sınıftaki diğer parametreleri türler için de kısıt konulabilir. Ancak bir parametreli tür için ancak sadece bir tane sınıf kısıt olabilir. Örneğin aşağıdaki Hashtable sınıfında DegerTuru Musteri sınıfından tremiş olması gerekirken, AnahtarTuru hem IComparable hemde IEnumerable arayüzünü uygulamış olması gerekir.
public class Hashtable<AnahtarTuru, DegerTuru> where
AnahtarTuru : IComparable
AnahtarTuru : IEnumerable
DegerTuru : Musteri
{
public void Add(AnahtarTuru anahtar, DegerTuru deger)
{
switch(anahtar.CompareTo(x))
{
}
}
}
AnahtarTuru : IComparable
AnahtarTuru : IEnumerable
DegerTuru : Musteri
{
public void Add(AnahtarTuru anahtar, DegerTuru deger)
{
switch(anahtar.CompareTo(x))
{
}
}
}
2 - Iterators
Bir dizinin elemanları üzerinde tek tek dolaşma işlemine iterasyon
denilmektedir. Koleksiyon tabanlı nesnelerin elemanları arasında tek yönlü dolaşmayı
sağlayan foreach döngü yapısının bizim tanımlayacağımız sınıflar için de kullanılabilmesi
için sınıfımızın bazı arayüzleri uyguluyor olması gerekir. foreach döngüsü
derleme işlemi sırasında while döngüsüne dönüştürülür. Bu dönüştürme işlemi
için IEnumerator arayüzündeki metotlardan ve özelliklerden faydalanılmaktadır.
Bu dönüştürme işleminin nasıl yapıldığına bakacak olursak :
ArrayList alist = new ArrayList();
foreach(object o in alist)
{
BiseylerYap(o);
}
// Yukarıdaki foreach bloğunun karşılığı aşağıdaki gibidir.
Enumerator e = alist.GetEnumerator();
while(e.MoveNext())
{
object o = e.Current
BiseylerYap(o);
}
foreach(object o in alist)
{
BiseylerYap(o);
}
// Yukarıdaki foreach bloğunun karşılığı aşağıdaki gibidir.
Enumerator e = alist.GetEnumerator();
while(e.MoveNext())
{
object o = e.Current
BiseylerYap(o);
}
foreach döngüs yapısı için gerekli olan arayüzlerin uygulanması
özellikle ağaç yapısı şeklindeki veri türleri için oldukça zordur. Bu yüzden C#
sınıfların foreach yapısı ile nasıl kullanılacağına karar vermek için yeni bir
yapı kullanacaktır.
Sınıflarda, foreach anahtar kelimesi bir metot ismi gibi kullanılarak sınıfın foreach döngüsünde nasıl davranacağını bildirebilriz. Her bir iterasyon sonucu geri döndürülecek değeri ise yield anahtar sözcüğü ile belirtilir. Örneğin her bir iterasyonda farklı bir tamsayı değeri elde etmek için sınıf bildirimi aşağıdaki gibi yapılabilir.
Sınıflarda, foreach anahtar kelimesi bir metot ismi gibi kullanılarak sınıfın foreach döngüsünde nasıl davranacağını bildirebilriz. Her bir iterasyon sonucu geri döndürülecek değeri ise yield anahtar sözcüğü ile belirtilir. Örneğin her bir iterasyonda farklı bir tamsayı değeri elde etmek için sınıf bildirimi aşağıdaki gibi yapılabilir.
public class Sınıf
{
public int foreach()
{
yield 3;
yield 4;
yield 5;
}
}
{
public int foreach()
{
yield 3;
yield 4;
yield 5;
}
}
Yukarıda bildirilen Sınıf türünden nesneler üzerinde foreach döngüsü
kullanıldığında iterasyonlarda sırasıyla 3,4 ve 5 sayıları elde edilecektir.
Buna göre aşağıdaki kod parçası ekrana 345 yazacaktır.
Sınıf deneme = new Sınıf();
foreach(int eleman in deneme)
{
Console.Write(eleman);
}
foreach(int eleman in deneme)
{
Console.Write(eleman);
}
Çoğu durumda foreach yapısı ile sınıfımızın içindeki bir dizi üzerinde
iteratif bir şekilde dolaşmak isteyeceğiz. Bu durumda foreach bildirimi içinde
ayrı bir foreach döngüsü aşağıdaki gibi kullanılabilir.
public class Sınıf
{
private int[] elemanlar;
public int foreach()
{
foreach(int eleman in elemanlar)
{
yield eleman;
}
}
}
{
private int[] elemanlar;
public int foreach()
{
foreach(int eleman in elemanlar)
{
yield eleman;
}
}
}
Yukarıdaki Sınıf nesneler ile foreach döngüsü kullanıldığında her bir
iterasyonda elemanlar dizisinin bir sonraki elemanına ulaşılır.
Gördüğünüz gibi programcının bildireceği sınıflar da foreach döngüs yapısını
kullanabilmek için eskiden olduğu gibi IEnumerator arayüzün uygulamaya gerek
kalmamıştır. Bu işlemi derleyici bizim yerimize yapar.
3 - Anonymous Metotlar(İsimsiz Metotlar)
3 - Anonymous Metotlar(İsimsiz Metotlar)
İsimsiz metotlar, bir temsilciye ilişkin kod bloklarını emsil eder.
Bildiğiniz gibi temsilciler yapısında metot referasnı tutan veri yapılarıdır.
Bir temsilci çağrımı yapıldığında temsilcinin temsil ettiği metot çalıştırılır.
Özellikle görsel arayüzlü programlar yazarken event tabanlı programlama tekniği
kullanılırken temsilcilerin kullanımına sıkça rastlanır. Örneğin bir Button
nesnesine tıklandığında belirli bir kod kümesinin(metot) çalıştırılması için
temsilci veri yapısından faydalanılır. Sözgelimi Button nesnesinin tıklanma
olayı meydana geldiğinde Click isimli temsilcisine yeni bir temsilci atanır. Ne
zaman button nesnesinin Click olayı gerçekleşse ardından hemen temsilcinin
temsil ettiği metot çağrılır. Buna bir örnek verecek olursak;
public class Form
{
Button dugme;
public Form
{
dugme = new Button();
dugme.Click += new EventHandler(OnClick);
}
void OnClick(object sender, EventArgs e)
{
....
}
}
{
Button dugme;
public Form
{
dugme = new Button();
dugme.Click += new EventHandler(OnClick);
}
void OnClick(object sender, EventArgs e)
{
....
}
}
Yukarıdaki koddan da görüldüğü üzere temsilci ile temsilcinin temsil
ettiği metotlar ayrı yerlerdedir. İsimsiz metotlarla bu işlemi biraz daha
basitleştirmek mümkündür. Temsilci oluşturulduktan sonra açılan ve kapanan
parantezler arasına temsilci çağrıldığında çalıştırılacak kodlar yazılabilir.
Yukarıdaki örneği isimsiz metot ile yapacak olursak :
public class Form
{
Button dugme;
public Form
{
dugme = new Button();
dugme.Click += new EventHandler(object sender, EventArgs e);
{
//çalıştırılacak kodlar.
};
}
}
{
Button dugme;
public Form
{
dugme = new Button();
dugme.Click += new EventHandler(object sender, EventArgs e);
{
//çalıştırılacak kodlar.
};
}
}
Tanımlanan kod bloğundan sonra noktalı vürgülün eklenmiş olduğuna dikkat
edin. Temsilci bloğundaki kodlar normal metotlardan biraz farklıdır. Normal kod
blokları ile benzer özellikler taşır. Yukarıdaki temsilci kod bloğunda, blok dışında
tanımlanan değişkenlere erişebilmek mümkündür. Ayrıca olay argümanlarının
da(sender,e) EventHandler türünün parantezleri içinde yazıldığınıda dikkat
edin. Bir önceki versiyonda olay argümanlarının yerine temsil edilen metodun
ismi yazılmıştı.
Peki isimsiz metotlar nasıl çalıştırılmaktadır? İsimsiz metot tanımı ile karşılaşan derleyici tekil isme sahip bir sınıf içinde tekil isme sahip bir metot oluşturur ve isimsiz metot gövdesindeki kodlara bu tekil metot içinden erişilir. Temsilci nesnesi çağrıldığında, derleyicinin ürettiği bu metot ile isimsiz metodun bloğundaki kodlar çalıştırılır.
Peki isimsiz metotlar nasıl çalıştırılmaktadır? İsimsiz metot tanımı ile karşılaşan derleyici tekil isme sahip bir sınıf içinde tekil isme sahip bir metot oluşturur ve isimsiz metot gövdesindeki kodlara bu tekil metot içinden erişilir. Temsilci nesnesi çağrıldığında, derleyicinin ürettiği bu metot ile isimsiz metodun bloğundaki kodlar çalıştırılır.
4 - Partial Types (Kısmi Türler)
Kısmi türler yardımıyla bir sınıfın elemanlarını farklı dosyalarda saklamak mümkündür. Örneğin Dosya1.cs ve Dosya2.cs aşağıdaki gibi olsun.
Kısmi türler yardımıyla bir sınıfın elemanlarını farklı dosyalarda saklamak mümkündür. Örneğin Dosya1.cs ve Dosya2.cs aşağıdaki gibi olsun.
//Dosya1.cs
public partial class deneme
{
public void Metot1
{
...
}
}
public partial class deneme
{
public void Metot1
{
...
}
}
//Dosya2.cs
public partial class deneme
{
public void Metot2
{
...
}
}
public partial class deneme
{
public void Metot2
{
...
}
}
Yukarıdaki iki
dosyayı aynı anda derlediğimizde eğer kısmi türler kavramı olmasaydı derleme
zamanında hata alırdırk. Çünkü aynı isim alanında birden fazla aynı isimli sınıf
bildirimi yapılmış. Halbuki kısmi türler ile bu iki sınıf bildirimi aynı sınıf
olarak ele alınır, ve birleştirilir. Yani deneme isimli sınıfın Metot1() ve
Metot2() adında iki tane metodu olmuş olur.
Bir türe ait elemanları tek bir dosya içinde toplamak Nesne Yönelimli Programlama açısından her ne kadar önemli olsada bazen farklı dosyalarla çalışmak kodlarımızın yönetilebilirliğini artırabilmektedir.
Bir türe ait elemanları tek bir dosya içinde toplamak Nesne Yönelimli Programlama açısından her ne kadar önemli olsada bazen farklı dosyalarla çalışmak kodlarımızın yönetilebilirliğini artırabilmektedir.
Hiç yorum yok:
Yorum Gönder