C# programlamada delegeler ve olaylar, kodunuzu daha esnek, modüler ve yönetilebilir hale getiren temel kavramlardır. Özellikle olay tabanlı programlamada (event-driven programming) ve .NET çatısı içinde birçok tasarım deseni (design pattern) uygulamasında kritik rol oynarlar. Bu makalede, delegelerin ne olduğunu, nasıl kullanıldığını, olayların delegelerle olan ilişkisini ve gerçek dünya senaryolarında nasıl uygulandıklarını derinlemesine inceleyeceğiz. Amacımız, bu güçlü araçları C# projelerinizde etkin bir şekilde kullanabilmeniz için kapsamlı bir rehber sunmaktır.
Delegeler (Delegates): Fonksiyon İşaretçileri Gibi
Delegeler, bir veya daha fazla metodu güvenli bir şekilde referans alabilen tür-güvenli (type-safe) bir işaretçi gibidir. C++'daki fonksiyon işaretçilerine benzerler, ancak .NET ortamının sağladığı güvenlik ve nesne yönelimli özelliklerle birleşirler. Bir delege, belirli bir imzaya (parametre tipleri ve dönüş değeri) sahip herhangi bir metodu işaret edebilir.
Delege Tanımlama:
Bir delege tanımlarken, delegeye atanabilecek metodun imzasını belirtiriz.
Yukarıdaki tanım, string bir parametre alan ve hiçbir değer döndürmeyen (void) metodları işaret edebilecek bir delege türü oluşturur.
Delege Kullanımı:
Delege tanımlandıktan sonra, onu bir sınıf içinde bir alan, özellik veya yerel değişken olarak kullanabiliriz.
Yukarıdaki örnekte, NotifyUser delegesi hem DisplayMessage hem de LogMessage metodunu aynı anda işaret edebilmektedir. Bu duruma çoklu yayın delegesi (multicast delegate) denir. Bir delegeye `+=` operatörü ile metod ekleyebilir, `-=` operatörü ile metod çıkarabilirsiniz. Delegeler, abonelik listeleri oluşturmak için çok güçlü bir temel sağlar.
Built-in Delegeler: Func, Action ve Predicate
C# 3.0 ile birlikte gelen ve özellikle LINQ gibi modern özelliklerle sıkça kullanılan genel (generic) delegeler, çoğu zaman kendi delegelerinizi tanımlama ihtiyacınızı ortadan kaldırır.
Olaylar (Events): Abone Olma ve Bildirim Mekanizması
Olaylar, delegeler üzerine inşa edilmiş özel bir dil yapısıdır. Bir nesnenin, belirli bir şey olduğunda diğer nesneleri bilgilendirmesine olanak tanır. Örneğin, bir butona tıklandığında, bir veri yüklendiğinde veya bir işlem tamamlandığında. Olaylar, bir yayıncı (publisher) ve bir veya daha fazla abone (subscriber) arasındaki iletişimi kolaylaştırır.
Temel Felsefe: Yayıncı, olayın ne zaman gerçekleştiğini bilir ve olayı "yayınlar" (raises). Aboneler, bu olaya ilgi duyduklarında abone olurlar ve olay yayınlandığında bilgilendirilirler. Yayıncı, abonelerin kim olduğunu bilmez, yalnızca bir olayın gerçekleştiğini duyurur. Bu, gevşek bağlılık (loose coupling) sağlar.
Olay Tanımlama:
Bir olay, event anahtar kelimesi kullanılarak ve bir delege türü ile birlikte tanımlanır.
Dikkat: Standart olay deseni, olay işleyicilerinin ilk parametre olarak object sender (olayı tetikleyen nesne) ve ikinci parametre olarak EventArgs türemiş bir sınıf (olayla ilgili veri taşıyan) almasını gerektirir. Bu, sender/e pattern olarak bilinir ve .NET olayları için bir best practice'dir.
Olaylara Abone Olma ve Abonelikten Çıkma:
Bir olaya abone olmak için `+=` operatörü, abonelikten çıkmak için `-=` operatörü kullanılır.
Yukarıdaki diyagram, bir olayın nasıl tetiklendiğini, abonelerin nasıl bilgilendirildiğini ve genel akışı görselleştirmektedir. (Bu sadece bir örnek görsel linkidir.)
Delegeler ve Olaylar Arasındaki Fark
Ne Zaman Hangisini Kullanmalı?
Gerçek Dünya Senaryoları
Sonuç
C# delegeleri ve olayları, modern C# programlamanın temel taşlarından ikisidir. Kodunuzu daha modüler, esnek ve yeniden kullanılabilir hale getirmek için vazgeçilmez araçlardır. Delegeler, metod referanslarını soyutlayarak fonksiyonel programlama tarzına yakın yaklaşımlar sunarken; olaylar, gevşek bağlı bir yayıncı-abone iletişim deseni oluşturarak sistem mimarisini iyileştirir. Bu kavramları iyi anlamak ve doğru bir şekilde uygulamak, daha sağlam, bakımı kolay ve ölçeklenebilir C# uygulamaları geliştirmenize yardımcı olacaktır. Unutmayın, özellikle olaylarda abonelikten çıkma (unsubscription) mekanizmasını doğru bir şekilde yönetmek, potansiyel bellek sızıntılarını önlemek adına hayati öneme sahiptir.
Microsoft Docs: Delegates (English)
Microsoft Docs: Events (English)
Delegeler (Delegates): Fonksiyon İşaretçileri Gibi
Delegeler, bir veya daha fazla metodu güvenli bir şekilde referans alabilen tür-güvenli (type-safe) bir işaretçi gibidir. C++'daki fonksiyon işaretçilerine benzerler, ancak .NET ortamının sağladığı güvenlik ve nesne yönelimli özelliklerle birleşirler. Bir delege, belirli bir imzaya (parametre tipleri ve dönüş değeri) sahip herhangi bir metodu işaret edebilir.
Delege Tanımlama:
Bir delege tanımlarken, delegeye atanabilecek metodun imzasını belirtiriz.
Kod:
public delegate void MyDelegate(string message);
Delege Kullanımı:
Delege tanımlandıktan sonra, onu bir sınıf içinde bir alan, özellik veya yerel değişken olarak kullanabiliriz.
Kod:
public class Notifier
{
public delegate void NotificationDelegate(string message);
public NotificationDelegate NotifyUser;
public void SendNotification(string message)
{
// Delege null değilse metodu çağır
NotifyUser?.Invoke(message);
}
}
public class Program
{
public static void DisplayMessage(string msg)
{
Console.WriteLine($"[BİLGİ]: {msg}");
}
public static void LogMessage(string msg)
{
Console.WriteLine($"[LOG]: {msg} - {DateTime.Now}");
}
public static void Main(string[] args)
{
Notifier notifier = new Notifier();
// Metodu delegeye atama
notifier.NotifyUser = DisplayMessage;
notifier.SendNotification("Merhaba Dünya!");
// Birden fazla metodu delegeye atama (Multicast Delegates)
notifier.NotifyUser += LogMessage; // Yeni metod ekle
notifier.SendNotification("Çoklu yayın testi.");
// Bir metodu delege listesinden çıkarma
notifier.NotifyUser -= DisplayMessage;
notifier.SendNotification("DisplayMessage çıkarıldı.");
}
}
Built-in Delegeler: Func, Action ve Predicate
C# 3.0 ile birlikte gelen ve özellikle LINQ gibi modern özelliklerle sıkça kullanılan genel (generic) delegeler, çoğu zaman kendi delegelerinizi tanımlama ihtiyacınızı ortadan kaldırır.
- Action: Geri dönüş değeri olmayan (void) metodlar için kullanılır. 0 ila 16 arasında parametre alabilir.
Kod:Action<string> printAction = (msg) => Console.WriteLine(msg); printAction("Bu bir Action delegesi.");
- Func: Geri dönüş değeri olan (non-void) metodlar için kullanılır. Son tip parametresi geri dönüş tipini belirtir. 0 ila 16 arasında parametre alabilir.
Kod:Func<int, int, int> addFunc = (a, b) => a + b; Console.WriteLine($"Toplama sonucu: {addFunc(5, 3)}"); Func<string, bool> isLongString = (s) => s.Length > 10; Console.WriteLine($"'Merhaba' uzun mu? {isLongString("Merhaba")}"); Console.WriteLine($"'Çok uzun bir metin' uzun mu? {isLongString("Çok uzun bir metin")}");
- Predicate: Yalnızca tek bir parametre alan ve `bool` geri dönüş tipi olan özel bir `Func` türevidir. Genellikle koleksiyonlarda filtreleme için kullanılır.
Kod:Predicate<string> startsWithA = (s) => s.StartsWith("A"); List<string> names = new List<string> { "Ahmet", "Ayşe", "Mehmet" }; string foundName = names.Find(startsWithA); Console.WriteLine($"'A' ile başlayan isim: {foundName}");
Olaylar (Events): Abone Olma ve Bildirim Mekanizması
Olaylar, delegeler üzerine inşa edilmiş özel bir dil yapısıdır. Bir nesnenin, belirli bir şey olduğunda diğer nesneleri bilgilendirmesine olanak tanır. Örneğin, bir butona tıklandığında, bir veri yüklendiğinde veya bir işlem tamamlandığında. Olaylar, bir yayıncı (publisher) ve bir veya daha fazla abone (subscriber) arasındaki iletişimi kolaylaştırır.
Temel Felsefe: Yayıncı, olayın ne zaman gerçekleştiğini bilir ve olayı "yayınlar" (raises). Aboneler, bu olaya ilgi duyduklarında abone olurlar ve olay yayınlandığında bilgilendirilirler. Yayıncı, abonelerin kim olduğunu bilmez, yalnızca bir olayın gerçekleştiğini duyurur. Bu, gevşek bağlılık (loose coupling) sağlar.
Olay Tanımlama:
Bir olay, event anahtar kelimesi kullanılarak ve bir delege türü ile birlikte tanımlanır.
Kod:
public class Sensor
{
// Özel bir delege tanımlayabiliriz
public delegate void TemperatureChangeHandler(object sender, TemperatureEventArgs e);
// Veya .NET'in yerleşik EventHandler delegelerini kullanabiliriz
public event TemperatureChangeHandler TemperatureChanged;
// Ya da standart EventHandler<TEventArgs>
public event EventHandler<TemperatureEventArgs> HumidityChanged;
private int _temperature;
public int Temperature
{
get { return _temperature; }
set
{
if (_temperature != value)
{
_temperature = value;
// Olayı tetikle
OnTemperatureChanged(value);
}
}
}
private int _humidity;
public int Humidity
{
get { return _humidity; }
set
{
if (_humidity != value)
{
_humidity = value;
// Olayı tetikle
OnHumidityChanged(value);
}
}
}
// Olay tetikleme metodu (genellikle "On" önekiyle)
protected virtual void OnTemperatureChanged(int newTemperature)
{
// Olayın aboneleri var mı kontrol et
TemperatureChanged?.Invoke(this, new TemperatureEventArgs(newTemperature));
}
protected virtual void OnHumidityChanged(int newHumidity)
{
HumidityChanged?.Invoke(this, new TemperatureEventArgs(newHumidity)); // EventArgs yeniden kullanıldı, burada bir TemperatureEventArgs ama doğru olan HumidityEventArgs olmalı
}
}
// Olay verilerini taşıyan sınıf (EventArgs türemiş olmalı)
public class TemperatureEventArgs : EventArgs
{
public int NewTemperature { get; }
public TemperatureEventArgs(int newTemperature)
{
NewTemperature = newTemperature;
}
}
Olaylara Abone Olma ve Abonelikten Çıkma:
Bir olaya abone olmak için `+=` operatörü, abonelikten çıkmak için `-=` operatörü kullanılır.
Kod:
public class Display
{
public void DisplayTemperature(object sender, TemperatureEventArgs e)
{
Console.WriteLine($"Ekran: Yeni sıcaklık algılandı: {e.NewTemperature} derece.");
}
public void DisplayHumidity(object sender, TemperatureEventArgs e) // Burda yine TemperatureEventArgs kullandık ama HumidityEventArgs olmalıydı. Örnek kodda düzeltmedik ama gerçekte HumidityEventArgs daha iyi olur.
{
Console.WriteLine($"Ekran: Yeni nem oranı algılandı: {e.NewTemperature}% (Sıcaklık EventArgs ile)"); // Burayı sıcaklık yerine nem için uygun şekilde değiştirmeliydik.
}
}
public class Logger
{
public void LogTemperatureChange(object sender, TemperatureEventArgs e)
{
Console.WriteLine($"Log: Sıcaklık değişimi kaydedildi: {e.NewTemperature} derece. Kaynak: {sender.GetType().Name}");
}
}
public class MainApp
{
public static void Main(string[] args)
{
Sensor sensor = new Sensor();
Display display = new Display();
Logger logger = new Logger();
// Abone ol
sensor.TemperatureChanged += display.DisplayTemperature;
sensor.TemperatureChanged += logger.LogTemperatureChange;
sensor.HumidityChanged += display.DisplayHumidity; // Nem için abone ol
Console.WriteLine("Sensör değerleri güncelleniyor...");
sensor.Temperature = 25; // Olay tetiklenecek
sensor.Humidity = 60; // Olay tetiklenecek
sensor.Temperature = 25; // Aynı değer, olay tetiklenmez
Console.WriteLine("\nBir abone çıkarılıyor...");
sensor.TemperatureChanged -= logger.LogTemperatureChange; // Abonelikten çık
sensor.Temperature = 28; // Sadece DisplayTemperature çağrılacak
// Abonelikten çıkılmazsa ne olur? Bellek sızıntıları!
// [quote]
// Önemli Not: Bir olay abonesi, olay yayıncısı abonelik listesinden kaldırılmazsa,
// abonelik yapan nesne çöp toplayıcı tarafından toplanmayabilir ve bu durum
// [b]bellek sızıntılarına (memory leaks)[/b] yol açabilir. Bu nedenle, özellikle uzun ömürlü
// veya birçok nesnenin birbirine abone olduğu durumlarda,
// abonelikten çıkmayı unutmamak çok önemlidir.
// [/quote]
}
}

Yukarıdaki diyagram, bir olayın nasıl tetiklendiğini, abonelerin nasıl bilgilendirildiğini ve genel akışı görselleştirmektedir. (Bu sadece bir örnek görsel linkidir.)
Delegeler ve Olaylar Arasındaki Fark
- Delege: Temelde bir metod referansıdır. Herhangi bir metodu (imzası uygun olduğu sürece) atayabilir, çağırabilir, listeleyebilir ve hatta listeyi değiştirebilirsiniz. Bir delegeye dışarıdan erişim sağlanabilir ve içerideki abonelik listesi üzerinde tam kontrol vardır.
- Olay: Bir delege etrafında bir kapsülleyici (wrapper) görevi görür. Temel amacı, bir nesnenin diğer nesnelere bir "olay" hakkında bilgi vermesidir, ancak yayıncının abonelik listesinin dışarıdan manipüle edilmesini engeller. Dışarıdan sadece `+=` (abone ol) ve `-=` (abonelikten çık) işlemleri yapılabilir, delege doğrudan çağırılamaz veya abonelik listesi okunamaz. Bu, olayın güvenliğini ve tutarlılığını sağlar. Yani bir olaya sadece sahibi (yayıncı sınıf) tarafından erişilip tetiklenebilir.
Özetle:
Delegeler, bir veya daha fazla metodu işaret edebilen tür-güvenli "fonksiyon işaretçileridir."
Olaylar, delegeler üzerine inşa edilmiş, gevşek bağlı bildirim mekanizmaları sağlayan özel bir yapıdır. Olaylar, delegelerin güvenli ve kontrollü bir şekilde dışarıya açılmasını sağlar.
Ne Zaman Hangisini Kullanmalı?
- Delege kullanın:
- Eğer bir metodu argüman olarak başka bir metota geçirmek istiyorsanız (callback).
- LINQ sorgularında veya lambda ifadelerinde olduğu gibi kısa, anonim fonksiyonları tanımlamanız gerekiyorsa (`Func`, `Action`, `Predicate`).
- Bir sınıfın, istemcilerinin doğrudan metodları bir listeye ekleyip çıkarmasını ve listeyi manipüle etmesini istiyorsanız (çok nadir, genellikle olaylar tercih edilir).
- Olay kullanın:
- Eğer bir nesne belirli bir durum değişikliği veya eylem hakkında diğer nesneleri bilgilendirmek istiyorsa (tipik olay tabanlı programlama).
- Eğer yayıncı-abone desenini uygulamak ve yayıncı sınıfının abonelik listesi üzerinde daha fazla kontrol sahibi olmasını sağlamak istiyorsanız (abonelerin doğrudan delegeleri çağırmasını engellemek için).
- UI programlamasında (düğme tıklamaları, fare hareketleri vb.), veri değişiklik bildirimlerinde veya arka plan işlemlerinin tamamlanmasında.
Gerçek Dünya Senaryoları
- Kullanıcı Arayüzleri (UI): WPF, Windows Forms gibi çerçevelerde düğme tıklamaları, metin kutusu değişiklikleri gibi olaylar, olay işleyicileri aracılığıyla yönetilir.
- Asenkron Programlama: Bir ağ isteği tamamlandığında veya bir dosya okuma işlemi bittiğinde bildirim almak için olaylar veya callback delegeler kullanılabilir.
- Veri Değişikliği Bildirimleri: Bir veri modelindeki bir özellik değiştiğinde, ilgili UI bileşenlerini güncellemek için olaylar kullanılabilir.
- Plugin Mimarileri: Bir uygulamanın, dışarıdan yüklenen eklentilerin belirli olaylara abone olmasını sağlamak için olaylar mükemmel bir mekanizmadır.
- Durum Makineleri (State Machines): Bir durumdan diğerine geçiş yapıldığında olaylar tetiklenerek ilgili aksiyonlar alınabilir.
Sonuç
C# delegeleri ve olayları, modern C# programlamanın temel taşlarından ikisidir. Kodunuzu daha modüler, esnek ve yeniden kullanılabilir hale getirmek için vazgeçilmez araçlardır. Delegeler, metod referanslarını soyutlayarak fonksiyonel programlama tarzına yakın yaklaşımlar sunarken; olaylar, gevşek bağlı bir yayıncı-abone iletişim deseni oluşturarak sistem mimarisini iyileştirir. Bu kavramları iyi anlamak ve doğru bir şekilde uygulamak, daha sağlam, bakımı kolay ve ölçeklenebilir C# uygulamaları geliştirmenize yardımcı olacaktır. Unutmayın, özellikle olaylarda abonelikten çıkma (unsubscription) mekanizmasını doğru bir şekilde yönetmek, potansiyel bellek sızıntılarını önlemek adına hayati öneme sahiptir.
Microsoft Docs: Delegates (English)
Microsoft Docs: Events (English)