5
Oca'15

Bonus: C# ile Delegate ve Event Kavramları (.NET, C#, C# ile OOP)

Delegeler(delegates) ve olaylar(events), bir durumun gerçekleştiği zamanda bir şeyler yapabilmemize imkan sağlarlar. Bu, her zaman örnek verilen butona tıklanması veya bir propertynin değerinin değişmesi, yeni bir nesne yaratılması gibi her türlü durum olabilir.

 

Kod normal akışında satır satır işlenir. Biz ise, delegate veya event kullanarak programın çalışma anında bu akışa ortak olan kod blokları oluşturabilmekteyiz. Event, delegelerin özelleşmiş bir halidir.

 

Delegeleri açıklamaya çalışarak devam edelim.

Delegates

Delegeler, bir veya birden fazla fonksiyonu işaret eden işaretçilerdir. Tanımlarında, işaret edebilecekleri fonksiyonların yapılarını barındırırlar.

public delegate int SomeDelegate(string someString);

Yukarıdaki tanımdan “delegate” anahtar kelimesini çıkardığımızda bir fonksiyon tanımından farkı olmadığını görebilirsiniz. Bu delege için public erişim belirleyicisi kullandık. Biliyoruz ki erişim belirleyiciyi burada belirtmeseydik varsayılan olarak private olacaktı.

 

Yine bu yukarıdaki tanım bize, SomeDelegate isimli delegenin, integer tipinden değer döndüren ve parametre olarak bir adet string alan fonksiyonları işaret edebileceğini söylüyor. Madem delegemiz böyle fonksiyonları işaret edebiliyor o zaman biz de böyle bir fonksiyon tanımlayıp nasıl işaret edebileceğine bakalım.

public int SomeFunction(string someString)
{
    ...
}

Delegemizin bu fonksiyonu işaret etmesi için ise aşağıdaki kodu yazmamız gerekiyor:

SomeDelegate someDelegate = new SomeDelegate(SomeFunction);

Tanımladığımız delege tipinden, SomeFunction fonksiyonunu işaret eden bir delege belleğe çıkardık. Bu kod satırından sonra bu delege bellekte yaşadığı sürece ona yeni fonksiyonları da işaret ettirebiliriz. Böylece bu delege birden fazla fonksiyonu işaret etmiş olur.

someDelegate += new SomeDelegate(OtherFunction);

Buradaki OtherFunction fonksiyonunun da interger tipte değer döndürüp bir adet string parametre alan bir fonksiyon olmak zorunda olduğunu unutmayalım.

 

Dikkatinizi “+=” operatörü çekmiş olmalı. Bu operatör ile delegenin daha önceden işaret ettiği fonksiyonları çıkarmadan yeni bir fonksiyonu da işaret etmesini sağladık. “+=” haricinde “-=”, “=” operatörleri de kullanılabilir.

Operatörler ve Anlamları

 

“+=” Delegenin daha önceden işaret ettiği fonksiyonları çıkarmadan yeni bir fonksiyonu da işaret etmesini sağlar.

 

“-=” Daha önceden delegenin işaret ettiği bir fonksiyonu artık işaret etmemesini sağlar. Delegenin zaten işaret etmediği bir fonksiyon için -= operatörü kullanıldığında bir problem olmayacaktır.

 

“=” Delegeye bu operatör ile fonksiyon bağlandığında, artık delege sadece bu fonksiyonu işaret edecektir. Delegeye yapılan ilk atamada sadece = operatörü kullanılabilir.

 

Delegerlere fonksiyon bağlarken operatörden sonra sadece fonksiyon adını belirtmemiz de yeterli olacaktır. Derleyici geri kalanı bizim yerimize halleder.

SomeDelegate someDelegate = SomeFunction;

Delegenin handler edilebilmesi (delegeye abone olan fonksiyonların çağrılması) için ise şu kodu yazmak yeterlidir.

int result = someDelegate("str");

result sadece son çağrılan fonksiyonun döndürdüğü değere eşit olacaktır. Abone fonksiyonlar sıra ile çağrılır. “str” stringi abone fonksiyonlara parametre olarak geçilecektir.

 

Biraz haraket

 

Şimdi ise bu anlatmaya çalıştıklarımı biraz ete kemiğe büründürme zamanı. Şöyle bir örnek uyduralım:

 

Biz birer yemek pişirme makinesiyiz. Biraz fazla yetenekli olduğumuz için yemek pişirmenin yanında yemeğin piştiğini haber de verebiliyoruz. Yazılımcımız ise bunu başarabilmek için delegate veya eventlardan yararlanmış.

 

Bir konsol projesi oluşturarak işe başlayalım. Ardından Cooker adında bir class tanımlayalım. Bir pişirme süresi alsın ve içerisinde pişirme işini yapacak bir fonksiyon olsun. Pişirme işi bittiğinde de bize haber versin.

class Cooker
{
    private int _cookingTime; // second.

    // Pişirme süresi ctor yardımıyla setlenir.
    public Cooker(int cookingTime)
    {
        this._cookingTime = cookingTime;
    }

    // Pişirme başlatıp, bittiğinde haber verecek fonksiyonumuz.
    public void CookNow(DinnerIsServedDelegate handler)
    {
        // Saniye sayacımız.
        int secondCounter = 0;

        // Pişir...
        while (_cookingTime >= secondCounter)
        {
            secondCounter++;
            Thread.Sleep(1000); // 1 saniye bekle.
        };

        // Yemek pişti!
        handler(DateTime.Now);
    }

    public delegate void DinnerIsServedDelegate(DateTime dt);
}

Classın en altındaki delege tanımına dikkat edelim. Void tipte değer döndüren(değer döndürmeyen demek tam doğru değil.), parametre olarak datetime alan bir delege tanımı.

 

CookNow metodunda, tanımladığımız delege cinsinden bir handler istedik. Yemek piştiğinde bu handler ile abonelerine(işaret ettiği fonksiyonlar) yemek pişti diyebilmek için. Bunu yaparak kulağımızı ters elle tutmuş olduk aslında. Eventler ile bu işi daha şık yapabiliriz. Yazının event kısmı da zaten bunu yapıyor olacak.

 

Yukarıdaki kod ile delegenin nasıl handler edildiğini de görmüş olduk. Aynı fonksiyon çağrımı yapar gibi delegeyi handler ettik. handler(DateTime.Now) çağrımından sonra bu delegeye abone olan tüm fonksiyonlar sıra ile işletilecektir. İşletilecek bu fonksiyonlar arasında değer dönen fonksiyonlar varsa sadece en son fonksiyonun değeri dönecek.

 

Biraz da bu classı kullanan main metoduna göz atalım.

class Program
{
    static void Main(string[] args)
    {
        // Sınıf içerisindeki delege tanımına, static elemanlara ulaşıldığı gibi
        // tip adı üzerinden ulaşıldığına dikkat edelim.
        // Oluşturduğumuz aşağıdaki handler, çalıştırıldığında OnDinnerIsServed
        // fonksiyonuna çağrıda bulunacak.
        Cooker.DinnerIsServedDelegate handler = OnDinnerIsServed;

        // 5 sn sürecek pişirme süresine sahip bir pişirici oluşturalım.
        Cooker cooker = new Cooker(5);
        cooker.CookNow(handler);

        Console.ReadKey();
    }

    // Yemek piştiğinde bu fonksiyon çağrılacak ve aşçıya uyarı verilecek.
    public static void OnDinnerIsServed(DateTime dt)
    {
        Console.WriteLine("Cooking finished at  {0}", dt.ToString());
    }
}

Buradaki handler’ı, Cooker classı içerisinde bir property olarak da tanımlayıp kullanabilirdik. Son durum şöyle olurdu:

 

Cooker.cs

class Cooker
{
    ...

    public void CookNow()
    {
        ...

        // Yemek pişti!
        this.DinnerIsServedHandler(DateTime.Now);
    }

    public delegate void DinnerIsServedDelegate(DateTime dt);
    public DinnerIsServedDelegate DinnerIsServedHandler; // Buraya dikkat.
}

Program.cs

Cooker cooker = new Cooker(5);
cooker.DinnerIsServedHandler = OnDinnerIsServed;
cooker.CookNow();

Aynı örneği, event kavramını açıkladıktan sonra event ile de yapmaya çalışalım.

Events

Eventlar delegelerin özelleşmiş halleridir. Bir olay gerçekleştiğinde abonelere haber vermeyi, olaylara abone olmayı daha şık hale getirirler.

 

Event tanımından başlayalım. Yapı şöyle: erişim belirleyici + event anahtar kelimesi + delegesi + event adı.

public event SomeDelegate SomeEvent;

Yukarıdaki örnek için bunu uygularsak şöyle yapmamız gerekirdi:

public delegate void DinnerIsServedDelegate(DateTime dt);
public event DinnerIsServedDelegate DinnerIsServedEventHandler;

Tek yaptığımız event anahtar kelimesini eklemek oldu. Artık bir eventimiz var. Delegeler ilk defa bir metodu işaret edeceğinde = kullanabiliyorduk. Eventler için ise sadece += ve -= kullanabiliriz. Bu durumda main metodu aşağıdaki gibi olmalı.

Cooker cooker = new Cooker(5);
cooker.DinnerIsServedEventHandler += OnDinnerIsServed; // = değil += kullanabildik.
cooker.CookNow();

Bu eventi fırlatmak için ise bir değişiklik yapmamıza gerek yok. Yine, this.DinnerIsServedHandler(DateTime.Now); satırı ile fırlatılacaktır.
Özetleyecek olursak: Hangi yapıda metodları işaret edebileceğini belirlediğimiz bir delege tanımı yapıyoruz ve sonra bu delegeyi kullanan bir event tanımı yapıp kullanıyoruz. Bu noktada c# bize standart bir yapı oluşturmak istemiş. Bizim delege tanımımız yerine geçecek EventHandler isimli bir delege oluşturmuş. Peki bu EventHandler delegesi hangi yapıdaki metodları işaret ediyor?

 

– EventHandler delegesi, void tipte değer dönen, object ve EventArgs tiplerinde iki adet parametre alan metodları işaret edebiliyor. Buradaki object parametresi genelde olaya maruz kalan nesnenin kendisidir. EventArgs ise olayla ilgili bazı verileri içerir. İşte bir standart sağlamış olduk.

 

Yukarıdaki örneğimiz için hemen EventHandler kullanımına geçelim.

public event EventHandler DinnerIsServedEventHandler;

Tabiki artık bu evente bağladığımız metodumuzun yapısını da değiştirmemiz gerektiğini biliyoruz.

public static void OnDinnerIsServed(Object obj, EventArgs e)
{
    ...
}

Ve tabiki event fırlatma şeklimizi de.

this.DinnerIsServedEventHandler(this, new EventArgs());

Delege tanımını ortadan kaldırdık. bunun yerine EventHandler delegesini ve bize sağladığı standart yapıyı kullandık. Bu standart yapıya göre de kodumuzu optimize ettik.

 

Hatırlayacağınız üzere biz yemeğin piştiği zamanı da abone metodlarımza söylüyorduk. Şimdi bunu EventArgs kullanarak halletmeye çalışacağız. C#’ın sağlamış olduğu EventArgs sınıfı aslında bizim için sadece bir kalıp. Biz ise bu kalıbı genişleteceğiz. Genişlettiğimiz kalıbın içerisine kendi event argümanlarımızı koyacağız. EventArgs sınıfı ise kodumuzun daha anlaşılabilir olmasının yanında eventimize, standart yapıdaki tüm metodlar tarafından abone olunabilmesini sağlayacak.

 

EventArgs sınıfını genişleterek kendi DinnerIsServedEventArgs sınıfımızı oluşturalım.

public class DinnerIsServedEventArgs : EventArgs
{
    public DateTime FinishTime { get; set; }
}

EventHandler delegesi parametre olarak EventArgs tipinden değer alan metodları işaret edebilmek üzere tasarlanmıştı. Biz ise şimdi DinnerIsServedEventArgs tipinden değer alan metodları işaret edebilmesini istiyoruz. Bunu yapabilmek için ihtiyacımız olan generic yapıyı(EventHandler<TEventArgs>) c# zaten  bize sağlamış durumda.

public event EventHandler<DinnerIsServedEventArgs> DinnerIsServedEventHandler;

Yemeğin piştiği zamanı az önce dinleyicilerimize(evente abone olan metodlar) gönderememiştik, gönderelim.

// Yemek pişti!
DinnerIsServedEventArgs args = new DinnerIsServedEventArgs();
args.FinishTime = DateTime.Now;
this.DinnerIsServedEventHandler(this, args);
public static void OnDinnerIsServed(Object obj, DinnerIsServedEventArgs e)
{
    Console.WriteLine("Cooking finished at  {0}", e.FinishTime);
}

Konuyu biraz toparlamak adına kodun bütün halini paylaşıyorum.

class Cooker
{
    private int _cookingTime; // second.

    // Pişirme süresi ctor yardımıyla setlenir.
    public Cooker(int cookingTime)
    {
        this._cookingTime = cookingTime;
    }

    // Pişirme başlatıp, bittiğinde haber verecek fonksiyonumuz.
    public void CookNow()
    {
        // Saniye sayacımız.
        int secondCounter = 0;

        // Pişir...
        while (_cookingTime >= secondCounter)
        {
            secondCounter++;
            Thread.Sleep(1000); // 1 saniye bekle.
        };

        // Yemek pişti!
        DinnerIsServedEventArgs args = new DinnerIsServedEventArgs();
        args.FinishTime = DateTime.Now;
        this.DinnerIsServedEventHandler(this, args);
    }

    public event EventHandler<DinnerIsServedEventArgs> DinnerIsServedEventHandler;
}

public class DinnerIsServedEventArgs : EventArgs
{
    public DateTime FinishTime { get; set; }
}

Program.cs

static void Main(string[] args)
{
    // 5 sn sürecek pişirme süresine sahip bir pişirici oluşturalım.
    Cooker cooker = new Cooker(5);
    cooker.DinnerIsServedEventHandler += OnDinnerIsServed;
    cooker.CookNow();

    Console.ReadKey();
}

// Yemek piştiğinde bu fonksiyon çağrılacak ve aşçıya uyarı verilecek.
public static void OnDinnerIsServed(Object obj, DinnerIsServedEventArgs e)
{
    Console.WriteLine("Cooking finished at  {0}", e.FinishTime);
}

Bonus: Metod Aracılığı İle Event Fırlatmak

 

Cooker sınıfımız içerisinde bir olay gerçekleşiyor ve bu olayı dinleyen metodlar olaydan haberdar oluyorlar. Cooker sınıfını base alan başka sınıflar da bu olaydan haberdar olmak isteyebilirdi. Evet bunu Cooker sınfının eventine abone olarak yapabilir ama biz, olay gerçekleştiğinde çağrılan ve isteyen child classların override ettiği bir metod yazarak bu işi daha şık hale getirebiliriz. Ekranın AfterLoad metodunu override edip ekran açıldıktan sonra yapılacakları override ettiğimiz metoda yazmak gibi.

 

Yapmamız gereken, eventi fırlattığımız yerde override edilebilir bir metod çağırmak ve eventi bu metod içerisinden fırlatmak.

class Cooker
{
    ...

    // Pişirme başlatıp, bittiğinde haber verecek fonksiyonumuz.
    public void CookNow()
    {
        ...

        // Yemek pişti!
        DinnerIsServedEventArgs args = new DinnerIsServedEventArgs();
        args.FinishTime = DateTime.Now;
        OnDinnerIsServed(args); // Artık bu adımda eventi fırlatacak metod çağrılıyor.
    }
    
    // Eventi fırlatacak metod.
    protected virtual void OnDinnerIsServed(DinnerIsServedEventArgs e)
    {
        EventHandler<DinnerIsServedEventArgs> handler = DinnerIsServedEventHandler;
        if (handler != null)
        {
            handler(this, e);
        }
    }

    public event EventHandler<DinnerIsServedEventArgs> DinnerIsServedEventHandler;
}

Eventi fırlatmakla görevli Cooker sınıfı içerisindeki OnDinnerIsServed metodunun protected virtual tanımlandığına dikkat edelim. Böylelikle child classlar tarafından override edilebilmesini sağladık.

 

Örnekleri bu linklerden indirebilirsiniz:

Delegate, Event

 

Kaynaklar:

– http://msdn.microsoft.com/tr-tr/library/system.eventhandler(v=vs.110).aspx

– http://www.codeproject.com/Articles/4773/Events-and-Delegates-Simplified

  • Umit

    Delegate ve event kavramlarını anlamak adına çok faydalı bir yazı olmuş. Teşekkürler.

Yeni makaleleri E-Mail ile takip edin!