18 Nisan 2011 Pazartesi

C# Delegate

 C# dilinde delegate kavramı C ve C++ dillerindeki fonksiyon işaretçilerine karşılık gelir. Yani delegate türünde bir değişken kendisine atanan bir fonksiyonun adresine sahiptir. delegate türü değişkenlerin C ve C++ dilindeki fonksiyon işaretçilerinden farkı Nesne Yönelimli Programlama Tekniğine uygun bir şekilde tasarlanmış olmaları, ve güvenli bir şekilde kullanılabilmeleridir. Burada güvenlik kullanımdan kastedilen, delagate türü değişkenlerin RAM üzerinde sadece yetkili oldukları yani kendilerini çalıştıran programın yer aldığı bellek bölgesine erişim hakları olduğudur.
 Bir delegate birden fazla fonksiyona işaret edebilir. Böyle bir durumda delegate çağrıldığında işaret ettiği bütün fonksiyonlar atanma sırası ile çalıştırılacaktır. Bir fonksiyonun delegate aracılığıyla çağırılabilmesi için delegate tanımlanırken belirtilen bütün özelliklere sahip olmalıdır. Aksi takdirde derleme anında hata meydana gelir. İsterseniz önce bir delegate nasıl tanımlanır onu görelim daha sonra küçük bir uygulamayla çalışma anında nasıl davrandığını inceleyelim.
 Delegate türü değişkenler Class içerisinde tanımlanabileceği gibi class dışında da tanımlanabilir.  işaret edeceği fonksiyon ise yine herhangi bir yerde tanımlanmış olabilir. Bu durumda yani delegate ve onun işaret edeceği fonksiyon farklı class tanımlamaları hatta farklı namespace içindeyse o fonksiyona erişimin olması gereklidir. Yani public bir fonksiyon olmalıdır. Fakat fonksiyon aynı class içinde tanımlanmışsa böyle bir zorunluluk yoktur.
 Delegate tanımlamasının herhangi bir yerde yapılabileceğinden bahsetmiştik. Aşağıdaki gibi bir kullanım geçerlidir ve hem derleme anında hem de çalışma anında hata vermez.
using System;
delegate void delegatedeneme (string metin, double d, double p);
namespace DelegateKullanimi
{
  class DelegateKullan
  {
    static void Main(string[] args)
    {

    }
  }
}

Yukarıdaki tanımlamadan parametre olarak 2 tane double türünden değer alan, geriye double türünden bir değer döndüren fonksiyonları temsil etmek üzere bir temsilci tanımladığımızı anlıyoruz. Burada delegate tanımlanırken erişim belirleyicisi kullanılmadığı dikkatinizi çekmiştir. Delegate türü değişlenler sadece "internal" veya "public" olarak tanımlanabilir. Varsayılan türü internal dir. Yani burda olduğu gibi erişim belirleyici belirtilmezse varsayılan değer yazılmış kabul edilir. internal erişim belirleyicisi delegate değişkenine aynı namespace içerisinden erişimi imkanı verir.
 Şimdi işi biraz daha ilerletelim. İki tane fonksiyonumuz olsun. Bunlardan biri parametre olarak bir string ve iki tane integer türü değişken alsın ve bu iki sayıyı çarpsın diğeri de yine parametre olarak bir string ve iki integer değişken alsın fakat üs alma işlemi yapsın. Kodlamaya geçelim

static void UsAl(string metin, double d, double p)
{
  Console.WriteLine(metin + " " + Math.Pow(d,p));
}
static void Carp(string metin, double d, double p)
{
  Console.WriteLine(metin + " " + d * p);
}

 Bu fonksiyonları test edelim çalışıp çalışmadıklarını görelim.
static void Main(string[] args)
{
  UsAl("Fonksiyon : ",6, 4);
  Carp("Fonksiyon : ",6, 4);
  Console.ReadLine();
}
 Kodlarımızı derliyoruz ve gördüğünüz gibi çalışıyor. Zaten çalışmamasını beklemiyorduk. Şimdi gelelim delegate değişkenimize bu fonksiyonları tanıtmaya. Yine "Main(string[] args)" fonksiyonu içinde delegate değişkenimizin türünden bir nesne oluşturuyoruz(delegate türü değişkenlerin Nesne yönelimli programlama tekniğine uygun tasarlandıklarından bahsetmiştik.). Fonksiyonumuz şu hale geldi.
static void Main(string[] args)
{
  delegatedeneme temsilci = new delegatedeneme(UsAl);
  UsAl("Fonksiyon : ",6, 4);
  Carp("Fonksiyon : ",6, 4);
  Console.ReadLine();
}
 Artık temsilci nesnemiz "UsAl(string metin,  double d, double p)" fonksiyonunu temsil ediyor. Hatırlarsanız delegate türü değişkenlerin birden fazla fonksiyona işaret edebildiklerinden bahsetmiştik. Bbunu sağlamak için operatorlerden faydalanabiliriz. Delegate değişkenine temsil etmesi için fonksiyon eklemek istediğimizde
 temsilci += Carp;
çıkarmak istediğimizde ise
 temsilci -= Carp;
komutlarını kullanabiliriz. Şimdi temsilcimize "Carp(string metin,  double d, double p)" fonksiyonunu da ekleyelim.
 temsilci += Carp; komutunu temsilci tanımlama satırından sonraki satıra ekliyoruz.
 Herşey güzel fonksiyonları oluşturduk, temsilcimizi oluşturduk, fonksiyonları temsilcimize tanıttık.. Peki bunları nasıl çalıştıracağız. Bunun iki şekilde yapabiliriz.
1.Yol :  Delegate nesnesinin "Invoke()" fonksiyonunu kullanırız.Parametreler bu kısımda girilir.
Örneğin;
 temsilci.Invoke("Temsilci : ", 6, 4);
2.Yol :  Doğrudan temsilciye parametre vererek temsil edilen fonksiyonları çalıştırabiliriz.
Örneğin;
 temsilci("Temsilci : ", 6, 4); Ben ikinci yöntemi kullanmak istiyorum.  Main(string[] args) fonksiyonumuzun en son hali şöyle
static void Main(string[] args)
{  
delegatedeneme temsilci = new delegatedeneme(UsAl);
UsAl("Fonksiyon : ", 6, 4);
Carp("Fonksiyon : ", 6, 4);
temsilci += Carp;
temsilci("Temsilci : ", 6, 4);
Console.ReadLine();
}
Kodlarımızı derleyip çalıştırdığımızda şöyle bir çıktı elde ediyoruz.

 Şu an hayal kırıklığı yaşıyor olabilirsiniz ama biraz daha sabrederseniz delegate kullanmanın nasıl bir avantaj olduğunu göreceksiniz. Şuan sadece kod karmaşasını ortadan kaldırmaya yardımcı olduğunu gördük. Peki performans nasıl acaba? Dilerseniz hemen bunu da test edelim. Bunun için class seviyesinde  "DateTime" türünde bir değişken oluşturuyoruz.
 static DateTime tarih; UsAl ve Carp fonksiyonlarını şu şekilde değiştiriyoruz.
static void UsAl(string metin,  double d, double p)
{
tarih = DateTime.Now;
Console.WriteLine(metin + Math.Pow(d,p));
Console.WriteLine("Sure : " + (DateTime.Now - tarih).TotalMilliseconds);
}
static void Carp( string metin, double d, double p)
{
tarih = DateTime.Now;
Console.WriteLine(metin + d * p);
Console.WriteLine("Sure : " + (DateTime.Now - tarih).TotalMilliseconds);
}
 
 Şu an fonksiyonlarımız ilk çalıştığı anda güncel tarih ve saat bilgilerini alacak. İkinci aşamada UsAl fonksiyonu verdiğimiz parametrelerdeki değerlere göre üs alma işlemini yapıp sonucu ekrana yazdıracak, Carp fonksiyonu ise yine verilen parametrelere göre çarpma işlemi yapıp sonucu ekrana yazdıracak, üçüncü aşama olarak tekrardan güncel tarih ve saat bilgisini alıp en son aldığı zaman bilgisi ile fonksiyon ilk çalıştığı anda aldığı zaman bilgisi arasındaki farkı hesaplayıp milisaniye cinsinden ekrana yazdıracak. Kodlarımızı tekrar derleyelim ve sonucu görelim fakat bu sefer daha büyük rakamlar kullanalım. ben d=60, p=100 şeklinde değiştirdim

 Resimde de görüldüğü gibi basit işlemlerde bir fark yok veya varsa bile sıfıra yakın bir fark oluşuyor. Fakat işlem zorluk derecesi arttıkça aradaki fark da artacaktır. O yüzden her zaman değil ama işlemciyi zorlayacak durumlarda delegate kullanmak faydalı olabilir. Delegate kullanım mantığı biraz pointer(işaretçi) kullanımına benzer fakat aralarında çok önemli bir fark vardır. Yazımızın başında da belirttiğimiz gibi delegate'ler Nesne Yönelimli Programlama tekniğine uygun olarak tasarlanmışlardır ve güvenli bir kullanım sunarlar. Bir sonraki makalemizde temsilcilerin bir başka kullanım yeri olan Events(olaylar)'ları inceleyeceğiz. Programcıların kendi oluşturdukları bileşenlere nasıl event oluşturabileceklerinden bahsedeceğiz.

Hiç yorum yok:

Yorum Gönder