|
C#'ın Gelecekteki Özellikleri |
|
Gönderiliyor lütfen bekleyin... |
|
|
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.
İ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.
class
Stack
{
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];
.
.
}
}
|
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,
şeklinde yığına eklenen veriyi tekrar elde etmek için
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.
class
Stack
{
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];
.
.
}
}
|
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>
yıgın = new Stack>; |
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>
yıgın = new Stack>;
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.
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.
public
class
Hashtable
{
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,Musteri>
hashtable = new Hashtable,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.
public
class
Hashtable
{
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))
{
}
}
} |
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.
public
class
Hashtable<AnahtarTuru, DegerTuru> where
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 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.
public
class
Sınıf
{
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);
}
|
Ç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;
}
}
}
|
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)
İ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)
{
....
}
}
|
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.
};
}
}
|
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.
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.
//Dosya1.cs
public partial class
deneme
{
public void Metot1
{
...
}
}
|
//Dosya2.cs
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.
Not : Bu yazı "MSDN Magazine" deki "Future Features of C#" başlıkla bildiri
baz alınarak hazırlanmıştır.
Makale:
C#'ın Gelecekteki Özellikleri C#, Visual C# ve .NET Sefer Algan
|
|
|
-
-
Eklenen Son 10
-
Bu Konuda Geçmiş 10
Bu Konuda Yazılmış Yazılmış 10 Makale Yükleniyor
Son Eklenen 10 Makale Yükleniyor
Bu Konuda Yazılmış Geçmiş Makaleler Yükleniyor
|
|