Makale Özeti

.NET frameworkun en temel mekanizmalarından olan delegates, Event Driven kod yazan herkesin öğrenmesi gereken bir mekanizma.

Makale


Temsilcilerin (Delegates) İşleyiş Yapısı
Temsilciler en temel fakat biraz eksik bir tanımla fonksiyon göstericileridirler. Çağırıldıklarında temsil ettikleri asıl fonksiyonu (callback function) işletirler. Eğer daha önce C/C++ ile çalışmışsanız bu tür bir işlevin gösterici (pointer) kavramı sayesinde sağlandığını hatırlarsınız. Fakat .Net Framework temsilciler ile bize basit bir fonksiyon göstericisinden daha fazlasını sunuyor. Bir temsilci çağırıldığında birden çok metodu işletebilir. İşletilecek olan metodlar bir nesneye bağlı (instance method) ya da statik metodlar (static method) olabilir. Ve tüm bunlardan öte temsilciler CLR’a bağlı olduklarından tür korumalıdır (type safe). Yani temsil edilen bir metodu ancak ve ancak doğru argümanları verdiğiniz takdirde çağırabilirsiniz.
Bir örnekle başlayalım;
using System;
using System.Windows.Forms;
public delegate void Temsilcim(string yazı); // Temsilcimizi bildiriyoruz.
 
class TemsilciSınıfı {
 
 public static void KonsolaYazdır(string yazı) {
 Console.WriteLine(yazı);
 }
 public static void PencereyeYazdır(string yazı) {
 MessageBox.Show(yazı);
 }
 [STAThread]
 static void Main(string[] args) {
 Temsilcim t = new Temsilcim(KonsolaYazdır);
  t = (Temsilcim) Delegate.Combine(t,
 new Temsilcim(PencereyeYazdı));   
 t("deneme");
 }
}
Bilmeniz gerek ilk şey .Nette herşeyin sınıflardan ve sınıflara dahil olan metodlardan oluştuğudur. Yani temsilci (delegate) olsun, özellik (property) olsun, olay (event) olsun, sıralama (enumeration) olsun, vs... aslında bunların hepsi programınızı derlediğinizde MSIL’de işlevine uygun birer sınıfa veya metoda dönüştürülür.
Temsilci ne oluyor peki? Yukarıdaki örnekte
public delegate void Temsilcim(string yazı);
bildirimi ile aslında Temsilcim adında bir sınıf yaratmış oluyoruz. Bu sınıfın görünümü eğer kendimiz yazacak olsaydık yaklaşık olarak aşağıdaki gibi olacaktı;
public class Temsilcim : System.MulticastDelegate {
 private object _target;
 private Int32 _methodPtr;
 private MulticastDelegate _prev;
public Temsilcim(object target, Int32 methodPtr) {...}
public void virtual Invoke(string yazı) {...}
}
Gördüğünüz gibi burda bir yapıcı fonksiyon (constructor) var. İki tane argüman alıyor. Ve birde sizin temsil edilmesini istediğiniz fonksiyon ile aynı parametreye veya parametrelere sahip Invoke adında bir metod oluşturuluyor.
Temsilcim t = new Temsilcim(KonsolaYazdır);
deyimini ile “t” temsilcisini tanımladığınızda aslında .Net derlerken şu işlemi yapıyor. Eğer temsil edilmesini istediğiniz metod bir nesneye aitse (yani statik değil ise) yapıcı fonksiyonu ilk parametresine nesnenin referansını, ikinci parametresine de işletilecek gerçek metodun adresini veriyor. (Bu adresi elde etme işlemi .Net Runtime tarafından sağlanıyor.) ve Temsilcim tipinde bir nesne oluşturmuş oluyor. Eğer temsil edilecek statik bir metod ise ilk parametreye, statik bir metod herhangi bir nesneye bağlı olmadığı için, null değerini atanıyor ve sadece metod adresi değer olarak ikinci parametreye veriliyor.
t("deneme");
dediğinizde ise aslında derleme işleminde bu kod t.Invoke("deneme") olarak çevriliyor. Invoke metodu kendi içinde yapıcı metodun parametrelerinde verdiğiniz değerlere göre temsil edilecek metodun yerini bellekte belirliyor ve kendisine verilen parametreleri kullanarak (Dediğimiz gibi bu parametreler temsil edilen metodunkiler ile aynı) bunu işletiyor.
Şu ana kadar anlatmış olduğumuz şekli ile temsilcilerin diğer dillerdeki fonksiyon göstericilerinden işlev olarak tür korumalı olmaları dışında bir fark göremedik. Fakat örnek kodu dikkatli incelerseniz
Temsilcim t = new Temsilcim(KonsolaYazdır);
deyimden sonra;
t = (Temsilcim) Delegate.Combine(t, new Temsilcim(PencereyeYazdır));  
şeklinde “t” temsilcisine atama yapıldığını göreceksiniz.  İşte burda .Net Framework’ün bize sunduğu yeni bir özelliği kullanmış oluyoruz. Bir temsilciye birden fazla temsil edilmesini istediğimiz metodu atıyoruz. Eğer bu tanımlamadan sonra t("deneme"); deyimini işletecek olursak hem KonsolaYazdır hem de PencereyeYazdır metodularını çağırmış olacağız. Peki bu nasıl oluyor?
Her temsilci kendi içinde üç tane özel (private) nesne barındırır. Bunlar Object tipindeki _target, Int32 tipindeki _methodPtr ve MulticastDelegate tipindeki _prev nesneleridir. _target ve _methodPtr yapıcı fonksiyonda argüman olarak verilmiş değerleri saklarlar. Bunlar daha önce de dediğimiz gibi Invoke metodu tarafından çağırma sırasında kullanılır. Burda asıl dikkat edilmesi 
gereken değişken _prev. “t” nesnesine ait _prev değeri
t = new Temsilcim (KonsolaYazdır);
tanımlamasından sonra henüz bir değer içermiyor.
t = (Temsilcim) Delegate.Combine(t, new Temsilcim(PencereyeYazdır));  
diyerek Delegate sınıfına ait statik Combine metodunu çağırdığımızda iki adet argüman veriyoruz. İlki elimizdeki “t” temsilcisi, ikincisi ise o anda oluşturduğumuz ve PencereyeYazdır metodunu temsil eden temsilci. Combine metodu ikinci argüman olarak verilen yeni oluşturulmuş temsilcinin _prev değişkenine “t” ‘nin adresini atıyor. Yani elimizde birbirine bağlı iki adet temsilci olmuş oluyor ve geri dönüş değeri olarakta ikinci argümanın adresini geri döndürüyor. Biz bunu tekrar “t” ’ye atadığımızda temsilcilerden oluşan uzunluğu iki olan bir bağlı listemiz (linked list) olmuş oluyor. Şimdi Invoke metodunun içine bakalım.
public class Temsilcim : System.MulticastDelegate {
 ...
 public void virtual Invoke(string yazı) {
 
 if(_prev != null) _prev.Invoke(yazı);
 _target._methodPtr(yazı);
}
}
Görüldüğü gibi herhangi bir temsilciye ait Invoke metodu temsil ettiği metodu çağırmadan önce herhangi bir başka temsilciye bağlı olup olmadığını kontrol ediyor. Eğer bağlı ise, yani _prev değişkeni başka bir temsilciyi gösteriyorsa., ilk olarak gösterdiği temsilcinin Invoke metodunu çağırıyor. Bu şekilde listedeki tüm temsilcilerin Invoke metodları sondan başa çağırılmış oluyor.
C# kullanıcısına not: Combine metodu yerine C#’ta += operatör metodunu kullanabilirsiniz. Örneğin;
 t += new Temsilcim(PencereyeYazdır);


Bir diğer bahsedilmesi gereken konu ise Delegate ve MulticastDelegate sınıfları arasındaki “fark”. .Net Framework beta2’ye kadar bu iki sınıf arasındaki fark şuydu. Delegate içinde _prev değişkenini barındırmaz. Yani eğer temsilcilerimiz yukarıdaki örneklerde MulticastDelegate değil de Delegate sınıfından türemiş olsalardı _prev değişkeni tanımlı olmadığı için bir temsilci sadece bir metodu çağırabilecekti. Peki neden böyle birşeye gerek duyulmuştu? İlk zamanlar Microsoft sorunu şöyle düşünmüştü. Eğer temsil edilen metod geriye bir değer döndürüyorsa birden çok metodu aynı andan çağırdığımızda sadece en son Invoke metodu çağrılan fonksiyonun geri dönüş değerini alabiliriz. Bu da dikkatsiz programcıları hataya itebilir. Böylelikle şöyle bir tanım yapılmıştı. Eğer temsil edilen metod geriye bir değer döndürüyorsa derleyici (compiler) Delegate sınıfından bir temsilci yaratır, eğer geri dönüş değeri yoksa (void) herhangi bir sorun olmayacağı için örneğimizde olduğu gibi MulticastDelegate sınıfından temsilciler yaratır. Kısacası eğer temsil etmek istediğiniz metod geriye bir değer döndürüyorsa aynı andan birden fazla metodu çağırma özelliğine sahip olamıyordunuz. Fakat final sürümüne kadar geçen sürede bunun insanların kafasını karıştırdığı görüldü. Çoğu insan aradaki farkı doğru anlayamadığı için hatalı kodlar oluştu. Aynı zamanda geri dönüş değeri olmasının böyle bir kısıtlama getirmiş olması da terskarşılandı ve Microsoft final sürümüne az bir zaman kala değişiklik yaptı ve herşeyi MulticastDelegate olarak tanımladı. Fakat çok az zaman olduğu için Delegate sınıfını Framework kütüphanesinden kaldıramadılar. Ancak bu değişiklik 2. sürümde işlevi MulticastDelegate ismi ise Delegate olarak yeniden tanımlanacak bir sınıf olarak karşımıza çıkıcak. Kodlarınızı yazarken iki sınıfı aynıymış gibi varsayın.

Not: Halen kodlarınızda bir çok yerde Delegate sınıfına ait metodları kullanıyorsunuz fakat bu kafanızı karıştırmasın.
 Bu Delegate sınıfının gerekli olmasından değil Microsoft’un geç kalmış olmasından kaynaklanıyor.
Sanırım temsilcilerin nasıl çalıştığına dair yeterli bilgiyi vermiş olduk. Konu ile ilgili daha detaylı bilgi için msdn.microsoft.com adresine başvurabilirsiniz.
Can Balioğlu ~ Balisis Yazılım - http://balisis.com 2002
Yazar hakkında:
Balisis Yazılım’ın kurucusu ve genel başkanıdır. Münih Teknik Üniversitesi Bilgisayar Bilimleri öğrencisidir. Aynı zamanda yazılım mühendisi sıfatıyla .Net teknolojilerine dayalı projelerde görev almaktadır.