C# İle Delegeler Kullanarak Olay Tabanlı Programlama Nasıl Yapılır?

C# İle Delegeler Kullanarak Olay Tabanlı Programlama Nasıl Yapılır?
C# İle Delegeler Kullanarak Olay Tabanlı Programlama Nasıl Yapılır?

Gereksinimler ve Ön Hazırlık

Bu rehberdeki kod örneklerini çalıştırabilmek için bilgisayarınızda .NET 8 veya .NET 9 SDK yüklü olmalıdır. Geliştirme ortamı olarak Visual Studio 2022 veya VS Code kullanabilirsiniz. C# dilinin modern özelliklerini (pattern matching, target-typed expressions) kullanacağımız için projenizin "Nullable Reference Types" özelliğinin etkinleştirilmiş olması önerilir.

  • .NET SDK (8.0 veya üzeri)
  • C# 12/13 uyumlu bir IDE
  • Temel nesne yönelimli programlama (OOP) bilgisi

Delege Nedir ve Nasıl Tanımlanır?

Delege, belirli bir imza (dönüş tipi ve parametre listesi) ile eşleşen metotlara referans tutan bir yapıdır. Bir delege tanımladığınızda, aslında o imzaya sahip tüm metotları kapsayan bir "sözleşme" oluşturmuş olursunuz. İşte en temel delege tanımlama ve kullanma örneği:

// Delege tanımı
public delegate void LogHandler(string message);

public class Logger {
    public void WriteLog(string message) {
        Console.WriteLine($"[Log]: {message}");
    }
}

// Kullanım
Logger logger = new Logger();
LogHandler handler = logger.WriteLog;
handler("Sistem başlatıldı.");

Yukarıdaki örnekte LogHandler tipinde bir delege tanımladık. Bu delege, string parametresi alan ve geriye değer döndürmeyen tüm metotları işaret edebilir. handler değişkeni, WriteLog metodunu temsil eden bir referans haline gelir.

Olay Tabanlı Programlamada Delegelerin Rolü

Olaylar (events), delegelerin etrafına sarılmış birer güvenlik katmanıdır. Bir delegeyi doğrudan dışarıya açmak, dış sınıfların delegenin içeriğini temizlemesine veya hatalı atamalar yapmasına neden olabilir. Olaylar ise sadece += (abone ol) ve -= (abonelikten çık) operatörlerine izin vererek kapsülleme (encapsulation) sağlar.

Bir olay tanımlarken genellikle EventHandler veya EventHandler genel (generic) delegeleri tercih edilir. Bu, .NET ekosisteminde standart bir yaklaşımdır.

Adım Adım Olay Tabanlı Bir Sistem Tasarlamak

Bir "Sıcaklık Sensörü" simülasyonu oluşturalım. Sensörün sıcaklığı kritik seviyeyi geçtiğinde, sisteme bağlı olan tüm izleyicilerin (ekran, veritabanı, uyarı sistemi) bu durumdan haberdar olmasını sağlayacağız.

public class TemperatureChangedEventArgs : EventArgs {
    public double NewTemperature { get; }
    public TemperatureChangedEventArgs(double temp) => NewTemperature = temp;
}

public class TemperatureSensor {
    // Olay tanımı
    public event EventHandler? ThresholdReached;

    public void SetTemperature(double temp) {
        if (temp > 30.0) {
            // Olayı tetikleme
            ThresholdReached?.Invoke(this, new TemperatureChangedEventArgs(temp));
        }
    }
}

Burada EventHandler kullanarak tip güvenli bir olay yapısı kurduk. ThresholdReached?.Invoke ifadesi, olaya abone olan kimse yoksa hata almamamızı (null check) sağlar.

Olaylara Abone Olma ve Yönetim

Olayları tanımladıktan sonra, bu olayları dinleyen "abone" sınıflar oluşturmalıyız. Abonelik işlemleri genellikle sınıfın yapıcı metodunda veya ihtiyaç duyulan yaşam döngüsü aşamasında yapılır.

public class AlarmSystem {
    public void OnThresholdReached(object? sender, TemperatureChangedEventArgs e) {
        Console.WriteLine($"Uyarı! Sıcaklık kritik seviyeye ulaştı: {e.NewTemperature}°C");
    }
}

// Test kodu
var sensor = new TemperatureSensor();
var alarm = new AlarmSystem();

// Abonelik
sensor.ThresholdReached += alarm.OnThresholdReached;

sensor.SetTemperature(35.5);

Bu örnekte, AlarmSystem sınıfı sensörden gelen veriyi dinler. Olay tetiklendiğinde OnThresholdReached metodu otomatik olarak çağrılır.

Karşılaştırma: Delege ve Olay Kullanımı

Özellik Delege (Delegate) Olay (Event)
Kapsülleme Düşük (Doğrudan atama yapılabilir) Yüksek (Sadece += ve -=)
Kullanım Amacı Metotları değişken olarak geçirmek Durum bildirimleri ve bildirim mekanizmaları
Güvenlik Dışarıdan müdahaleye açık Sadece tanımlayan sınıf tetikleyebilir

Güvenlik Uyarısı: Olay aboneliklerinde "Memory Leak" (bellek sızıntısı) riskine dikkat edilmelidir. Eğer bir nesneye abone olduysanız ve artık ona ihtiyacınız yoksa, -= operatörü ile aboneliği mutlaka sonlandırın. Aksi halde çöp toplayıcı (Garbage Collector) nesneyi bellekten temizleyemez.

Yaygın Hatalar ve Debug İpuçları

Olay tabanlı programlamada en sık yapılan hata, null referans hatası almaktır. Olayı tetiklemeden önce mutlaka ?.Invoke sözdizimini kullanın. Ayrıca, çok kanallı (multi-threaded) uygulamalarda olay tetiklenirken abonelerin eşzamanlı değişmesi durumunda lock mekanizması kullanmanız gerekebilir.

// Güvenli tetikleme örneği
private void RaiseEvent() {
    var handler = ThresholdReached; // Yerel kopyalama (Thread safety için)
    handler?.Invoke(this, new TemperatureChangedEventArgs(40));
}

Sıkça Sorulan Sorular

Delegeler ile Lambda ifadeleri arasındaki fark nedir?

Delegeler bir türdür (tip), Lambda ifadeleri ise bu türü oluşturmanın modern ve kısa bir yoludur. Bir delegenin beklendiği her yerde Lambda ifadesi kullanabilirsiniz.

Bir olay birden fazla metodu tetikleyebilir mi?

Evet, delegeler "multicast" (çoklu yayın) özelliğine sahiptir. += ile birden fazla metodu aynı olaya bağlayabilirsiniz.

EventHandler kullanmak zorunda mıyım?

Hayır, kendi özel delege tipinizi tanımlayabilirsiniz ancak standartlara uyum ve okunabilirlik için EventHandler kullanımı önerilir.

Olayları sınıf dışından tetikleyebilir miyim?

Hayır, C# dilinde bir olay sadece tanımlandığı sınıf içerisinden tetiklenebilir. Bu, nesne yönelimli tasarımın bir gereğidir.

Action ve Func delegeleri nedir?

Bunlar, .NET içinde yerleşik olarak gelen genel delegelerdir. Action geriye değer döndürmeyen, Func ise geriye değer döndüren metotlar için kullanılır.

Yasal Uyarı ve Sorumluluk Reddi: Bu makaledeki kod örnekleri eğitim amaçlıdır. Üretim ortamında kullanacağınız kritik sistemlerde, özellikle asenkron olay işleyicilerinde hata yönetimi (try-catch blokları) ve performans izleme araçlarını mutlaka entegre ediniz. Kodun güvenliği ve sistem kararlılığı geliştiricinin sorumluluğundadır.

Olay Tabanlı Sistemlerde Performans Optimizasyonu ve Bellek Yönetimi

Olay tabanlı programlamada en sık karşılaşılan sorunlardan biri "bellek sızıntıları" (memory leaks) olarak bilinen durumdur. Bir nesne, başka bir nesnenin olayına abone olduğunda, olay kaynağı (publisher) abone olan nesneye (subscriber) bir referans tutar. Eğer abone olan nesne işini bitirdiğinde abonelikten çıkmazsa (unsubscribe), çöp toplayıcı (Garbage Collector) bu nesneyi bellekten temizleyemez.

Performansı korumak ve bellek sızıntılarını önlemek için şu stratejileri uygulamalısınız:

  • Abonelikten Çıkma (Unsubscribe): Nesne yaşam döngüsü sona erdiğinde mutlaka -= operatörünü kullanarak aboneliği sonlandırın.
  • Zayıf Referanslar (Weak References): Çok kritik sistemlerde WeakEventManager yapısını kullanarak olay kaynağının abone üzerinde güçlü bir referans tutmasını engelleyebilirsiniz.
  • Olay İşleyici Karmaşıklığı: Olay işleyicileri (event handlers) içerisinde ağır işlemler yapmaktan kaçının. Eğer işlem uzun sürecekse, bunu arka plan iş parçacığına (Task/Thread) devredin.
public class VeriIsleyici : IDisposable
{
    private VeriKaynagi _kaynak;

    public VeriIsleyici(VeriKaynagi kaynak)
    {
        _kaynak = kaynak;
        _kaynak.VeriGuncellendi += OnVeriGuncellendi;
    }

    private void OnVeriGuncellendi(object sender, EventArgs e)
    {
        // İşlem mantığı
    }

    public void Dispose()
    {
        // Bellek sızıntısını önlemek için kritik adım
        _kaynak.VeriGuncellendi -= OnVeriGuncellendi;
    }
}

Olay Tabanlı Tasarımlarda Birim Test (Unit Testing) Stratejileri

Olay tabanlı kodları test etmek, doğrudan metot çağırmaya göre biraz daha karmaşıktır. Olayların tetiklenip tetiklenmediğini doğrulamak için "Mocking" kütüphaneleri (Moq, NSubstitute gibi) kullanmak en etkili yöntemdir. Testlerinizde, olayın tetiklendiğini doğrulamak için bir "flag" (bayrak) değişkeni veya bir "event aggregator" kullanabilirsiniz.

Aşağıdaki örnek, bir olayın tetiklendiğini birim test içerisinde nasıl doğrulayabileceğinizi gösterir:

[Test]
public void VeriKaynagi_IslemYapildiginda_OlayTetiklenmelidir()
{
    // Arrange
    var kaynak = new VeriKaynagi();
    bool olayTetiklendi = false;
    kaynak.VeriGuncellendi += (s, e) => olayTetiklendi = true;

    // Act
    kaynak.IslemYap();

    // Assert
    Assert.IsTrue(olayTetiklendi, "Olay tetiklenmedi!");
}

Bu yaklaşım, sisteminizin olay akışının beklendiği gibi çalıştığından emin olmanızı sağlar. Karmaşık senaryolarda, olayların sırasını ve gönderilen parametrelerin doğruluğunu kontrol eden daha gelişmiş test senaryoları kurgulamanız önerilir.

Sonuç

C# ile delegeler kullanarak olay tabanlı programlama yapmak, uygulamalarınızı daha modüler, test edilebilir ve esnek hale getirir. Bu rehberde, delegelerin temelinden başlayarak olayların nasıl güvenli bir şekilde yönetileceğine kadar önemli adımları inceledik. Bir sonraki adım olarak, "Asenkron Olay İşleme" (Asynchronous Event Handling) ve "Reactive Extensions (Rx.NET)" konularını araştırarak olay yönetimini bir üst seviyeye taşıyabilirsiniz.

Bu yazıya tepkinizi paylaşın:
Burak Çelik

Doğa sporları, kampçılık ve açık hava aktiviteleri konularında uzmanlaşmış bir rehber yazarıyım. Yeni başlayanlar için güvenli ve keyifli bir deneyim sunacak, teknik bilgisi yüksek içerikler üretiyorum.

Yorumlar (0)

Yorum Yaz