Herkese merhaba. Bu yazıda kullanıcı etkileşimli programlama yaparken oldukça sık karşımıza çıkan delegate ve event konularını inceleyeceğiz. Bu ders için hazırladığım örnekleri Windows Forms projesi ile hazırladım. Daha öncesinde LINQ kullandıysanız Where() ya da Select() gibi metotların içerisine yazdığımız lambda expressionların da delegate yapısını kullandığını görmüşsünüzdür. İlerleyen yazılarda o konulara da değineceğiz. Daha fazla kafa karıştırmadan konuyu incelemeye başlayalım. Yazıda bulunan örneklere github hesabımdan ulaşabilirsiniz.

Delegate ve Event Yapıları

Delegate Nedir?

C# içerisinde tanımlanan metotlar da değişkenler gibi bellekte tutulurlar. Delegateler, metotların bellek adreslerini dolayısıyla metotları içerisinde barındırabilen yapılardır. Metotların bellek adresleri değişkenlerden farklıdır ve metotların adreslerini runtime’da göremeyiz. Delagete C# içerisinde bir tip olarak tanımlanır. Dolayısıyla, kendisi de referans tiplidir. Delegate temsil edeceği metodun imzasına uymak zorundadır. [Erişim belirleyicisi] delegate [geri dönüş tipi] [delegate ismi] (parametre) şeklinde tanımlanır.

Delegate Mapping

Delegate yapısı genellikle bellekteki metotların bir olay sonucunda çalıştırılması için kullanılırlar. Örneğin; veritabanından bir kayıt silindiğinde ilgili başka bir metodun çalıştırılması için delegate yapısını kullanırız. Aynı zamanda Winforms, WPF ve UWP gibi görsel arayüz programlarında bulunan bir nesne

ilgili metodun çalışması için de delegate kullanılır. Örnek olarak form üzerindeki butona basıldığında ekrana mesaj veren bir uygulama yapalım.

//bu delegate'i sadece bu alanda kullanacağımız için namespace altında delegate tanımı yapılır
delegate void MyDelegateHandler();
public partial class DelegateForm : Form
{
    public DelegateForm()
    {
        InitializeComponent();
    }

    MyDelegateHandler handler;
   
    private void DelegateForm_Load(object sender, EventArgs e)
    {
        handler = new MyDelegateHandler(HelloWorld);
    }

    void HelloWorld()
    {
        MessageBox.Show("Hello world!");
    }

    private void greetButton_Click(object sender, EventArgs e)
    {
        handler();
    }
}

Burada ilk önce MyDelegateHandler adında bir delegate oluşturduk. Daha sonrasında DelegateForm class’ının altında bu delegate’in bir değişkenini oluşturduk. Projemiz WinForm projesi olduğu için formun ekrana basılacağı sırada tetiklenecek olan DelegateForm_Load metodu içerisinde handler’ı tanımlıyoruz. (Tanımlama işlemini illa Load metodu içerisinde yapmak zorunda değiliz. greetButton_Click metodu içerisinde de tanımlama yapabilirdik.) MyDelegateHandler içerisine HelloWorld metodunu parametre olarak tanımlıyoruz. Greet butonuna her basıldığında Click metodu tetiklenecek ve Click metodu her tetiklendiğinde MyDelegateHandler içerisinde tanımlanmış olan tüm metotlar sırasıyla tetiklenecek.

DelegateHandler

Delegate içerisine birden fazla metot ekleyebilir veya çıkartabiliriz. Delegate içerisine eklenen ilk metot ilk önce çalışır. Örnek olarak içerisine 3 metot ekleyip/çıkartabileceğimiz bir delegate oluşturalım.

delegate void MyHandler();

static class Helper
{
    public static MyHandler Handler{ get; set; }

    public static void AddMethod(MyHandler myHandler)
    {
        Handler+= myHandler;
    }

    public static void RemoveMethod(MyHandler myHandler)
    {
        Handler-= myHandler;
    }

    public static void Invoke()
    {
        Handler();
    }
}

private void DelegateForm_Load(object sender, EventArgs e)
{
    Helper.AddMethod(MethodOne);
    Helper.AddMethod(MethodThree);
    Helper.AddMethod(MethodTwo);
    Helper.RemoveMethod(MethodTwo);
}

void MethodOne()
{
    MessageBox.Show("First Method");
}

void MethodTwo()
{
    MessageBox.Show("Second Method");
}

void MethodThree()
{
    MessageBox.Show("Third Method");
}

private void btnHandle_Click(object sender, EventArgs e)
{
    Helper.Invoke();
}

Burada Helper class’ı içerisinde oluşturduğumuz MyHandler delegate’ine dinamik bir şekilde metot ekleyip çıkartabiliyoruz. Handle butonuna tıklandığında delegate’i tetikleyecek olan Invoke metodunu çağırıyoruz. Load metodunda yaptığımız remove işlemini kaldırırsak metotların eklendiği sırayla (1-3-2 şeklinde) çağırıldığını görürüz. Burada metot ekleme işleminden hemen sonra metot kaldırıldığı için 2. metodun çağırıldığını görmüyoruz.

Delegate Sırası

Başka bir örnek ile eklediğimiz bu metotların içerisinde nasıl gezebileceğimize bakalım. Kullanıcıdan 2 adet sayı alıp bunlarla toplama ve çarpma işlemi yapan basit bir hesap makinesi tasarlayalım.

delegate int CalculatorHandler(int num1, int num2);
public partial class DelegateForm : Form
{
    public DelegateForm()
    {
        InitializeComponent();
    }

    CalculatorHandler calculatorHandler;
    
    private void DelegateForm_Load(object sender, EventArgs e)
    {
        calculatorHandler = new CalculatorHandler(Sum);
        calculatorHandler += Multiple;
        //calculatorHandler -= Multiple;
    }

    int Sum(int num1, int num2)
    {
        return num1 + num2;
    }

    int Multiple(int num1, int num2)
    {
        return num1 * num2;
    }

    private void calculateBtn_Click(object sender, EventArgs e)
    {
        int num1 = int.Parse(txtNum1.Text);
        int num2 = int.Parse(txtNum2.Text);

        MessageBox.Show(calculatorHandler(num1, num2).ToString());
        Delegate[] delegates = calculatorHandler.GetInvocationList();

        //Delegate Method details

        foreach (Delegate item in delegates)
        {
            MessageBox.Show("Method name: " + item.Method.Name);
            MessageBox.Show("Method return type: " + item.Method.ReturnType);
            int result = (int)item.DynamicInvoke(num1, num2);
            MessageBox.Show("Method result: " + result.ToString());
        }
    }
}

Her delegate içerisinde GetInvocationList metodu barındırır. Bu metot sayesinde delegate içerisinde tanımlı olan bütün metotları gezebiliriz.

Delegate Metot Listesi

Github projesinin içerisinde GetInvocationList metodunun farklı kullanım şeklini bulabilirsiniz.

Event Nedir?

Event; genellikle kullanıcının arayüz ile etkileşime girmesiyle oluşur. Bir event’in tanımlanabilmesi için mutlaka delegate ile eşleşmesi gerekir. Basitçe ifade etmek gerekirse, olayı yakalayıp değerlendirecek olan nesne, event’e bağlanır. Event’i tetikleyen nesne ise bu event’i çağıracak olan metoda bağlanarak event nesnesini tetikler. Event’ler için en temel delegate EventHandler’dır. Geri dönüş tipi olmayan ve parametreleri object veya EventArgs olan bütün eventleri içerisinde tutar.

Kendi event’lerimizi tanımlıyorsak, diğer nesneler tarafından ele alınması için manuel olarak tetiklememiz gerekir. Örneğin; araba nesnesi maksimum hızını aştığında tetiklenip ekrana aşırı hız sınırı mesajı veren bir uygulama tasarlayalım.

delegate void MyEventHandler(Car sender, CarEventArgs e);
internal class Car
{
    public string Brand { get; set; }
    public int Speed { get; set; }
    public int MaxSpeed { get; set; }

    public event MyEventHandler reachedMaxSpeedEvent;

    public void SpeedUp()
    {
        Speed += 5;
        if (Speed >= MaxSpeed)
        {
            if (reachedMaxSpeedEvent != null)
            {
                CarEventArgs e = new CarEventArgs();
                e.Speed = MaxSpeed;
                reachedMaxSpeedEvent(this, e);
            }
        }
    }
}

class CarEventArgs : EventArgs
{
    public int Speed { get; set; }
}

public partial class EventForm : Form
{
    public EventForm()
    {
        InitializeComponent();
    }

    Car myCar = new Car();

    private void timer1_Tick(object sender, EventArgs e)
    {
        myCar.SpeedUp();
        lblInfo.Text = $"{myCar.Brand} arabası {myCar.Speed} ile gidiyor.";
    }

    private void EventForm_Load(object sender, EventArgs e)
    {
        myCar.Brand = "Dacia";
        myCar.Speed = 50;
        myCar.MaxSpeed = 90;

        myCar.reachedMaxSpeedEvent += MyCarHasReacedMaxSpeed;
    }

    void MyCarHasReacedMaxSpeed(object sender, EventArgs e)
    {
        Car currentCar = sender as Car;
        CarEventArgs args = e as CarEventArgs;
        timer1.Stop();
        MessageBox.Show($"{currentCar.Brand} arabası aşırı hız yaptı. Hız: {args.Speed}");
    }
}

Car sınıfı MyEventHandler türünden bir event property’sine sahip. Car nesnesinin hızını SpeedUp metodu ile arttırıp event’in tetikleneceği koşullar gerçekleşmiş mi diye kontrol ediyoruz. Araba maksimum hıza ulaştığında, tetiklenecek olan event null değilse CarEventArgs türünde bir argüman tanımlayıp bunu tetiklenecek olan metoda ekliyoruz. Bu sayede event tetiklendiğinde car nesnesiyle ilgili gerekli bilgileri taşıyabiliyoruz. Form yüklenirken bu event’e MyCarHasReacedMaxSpeed metodunu bağlıyoruz. Ekranda da bu metodun içerisinde yazan mesajı görüyoruz.

EventTrigger

Benim delegate ve event yapıları için notlarım bu kadar. Aslında bu konuyla birlikte C# temellerini tamamlamış oluyoruz. Bundan sonraki yazılarımda ileri programlama teknikleri ve güncel teknolojiler hakkında yazılar oluşturmayı düşünüyorum. Bir sonraki yazıya kadar kendinize çok iyi bakın! 😊