C# programlama dili eğitim serimizde nesne yönelimli programlamanın (OOP) en kritik ve öğrenildiğinde size kelimenin tam anlamıyla seviye atlatacak iki konseptine geldik: Delegates (Temsilciler) ve Events (Olaylar).
Bir Windows masaüstü uygulamasında (WPF/WinForms) bir butona tıkladığınızda arka planda bir kodun çalışmasını sağlayan mekanizma nedir? Veya bir oyunda (Unity) karakterinizin canı sıfıra indiğinde, “Oyun Bitti” ekranının açılmasını, seslerin susmasını ve skor tablosunun güncellenmesini sağlayan o görünmez haberleşme ağı nasıl kurulur?
İşte tüm bu senaryoların kalbinde Delegate ve Event yapıları yatar. Bu rehberimizde, ilk defa bu konularla karşılaşanlar için temsilcilerin ve olayların mantığını, neden kullanıldıklarını ve gerçek hayat senaryolarındaki kodlamalarını en ince ayrıntısına kadar inceleyeceğiz.
1. Delegate (Temsilci) Nedir?
Normal şartlarda programlamaya başladığınızda, değişkenlerin (variables) sadece veri tuttuğunu öğrenirsiniz. Örneğin bir int değişkeni 25 sayısını tutar, bir string değişkeni "Merhaba" metnini tutar. Peki, bir değişkenin içine sayı veya metin yerine “başka bir metodu (fonksiyonu)” koymak isterseniz ne olur?
İşte C# dilinde delegateler (temsilciler), en basit tabirle güçlü tiplere sahip fonksiyon işaretçileridir (strongly-typed function pointers). Yani Delegate’ler, metodların referanslarını (bellek adreslerini) kendi içlerinde saklayabilen özel değişken türleridir.
Kullanım Kuralı: Bir delegate, sadece ve sadece kendi imzasına (geri dönüş tipi ve aldığı parametreler) uyan metotları taşıyabilir. Bu kurala “Tip Güvenliği” (Type Safety) denir.
Delegate Nasıl Tanımlanır ve Kullanılır?
Diyelim ki sistemimizde kullanıcılara mesaj gönderen farklı metotlarımız var. Kimi zaman mesajı ekrana, kimi zaman bir dosyaya yazdırmak istiyoruz. Bunu delegate kullanarak nasıl yöneteceğimize bakalım:
using System;
// 1. ADIM: Delegate Tanımlaması
// Bu delegate, dışarıdan bir 'string' alan ve geriye hiçbir şey döndürmeyen (void) tüm metotları temsil edebilir!
public delegate void MesajTemsilcisi(string mesaj);
class Program
{
// Ekrana yazdıran metot (Delegate'in imzasına uyuyor: void ve string parametreli)
static void EkranaYazdir(string m)
{
Console.WriteLine($"EKRAN ÇIKTISI: {m}");
}
// Log dosyasına yazdıran hayali metot (Yine delegate'in imzasına uyuyor)
static void DosyayaYazdir(string m)
{
Console.WriteLine($"DOSYAYA KAYDEDİLDİ: {m}");
}
static void Main()
{
// 2. ADIM: Delegate'den bir nesne (değişken) oluşturup içine 'EkranaYazdir' metodunu atıyoruz
MesajTemsilcisi benimTemsilcim = EkranaYazdir;
// 3. ADIM: Delegate'i çalıştırıyoruz. (Arka planda EkranaYazdir çalışır)
benimTemsilcim("C# öğrenmek çok keyifli!");
// Şimdi aynı değişkenin içine farklı bir metot atayalım:
benimTemsilcim = DosyayaYazdir;
benimTemsilcim("Bu mesaj log dosyasına gidecek.");
}
}
Multicast (Çoklu) Delegates
Temsilcilerin en büyük gücü, içlerinde birden fazla metodu aynı anda tutabilmeleridir! Eğer atama yaparken = yerine += operatörünü kullanırsanız, delegate çağrıldığında içine eklenen tüm metotlar sırasıyla çalışır.
MesajTemsilcisi cokluTemsilci = EkranaYazdir;
cokluTemsilci += DosyayaYazdir; // İkinci metodu da listeye ekledik!
cokluTemsilci("Aynı anda hem ekrana hem dosyaya yazılacak!");
2. Event (Olay) Nedir?
Delegate kavramını anladıysak, Event’leri anlamak çocuk oyuncağıdır. Olaylar (Events), temelde delegate’lerin üzerine inşa edilmiş, dışarıdan müdahalelere karşı güvenliğe alınmış bir yapıdır.
C#, “yayınla-abone ol” (publish-subscribe) tarzı bir yapı sunmak için olay (event) semantiklerine özel olarak sahiptir ve bu işlemleri gerçekleştirmek için altyapıda her zaman temsilcileri (delegates) kullanır. Başka bir deyişle, C++ kütüphanelerinde (örneğin Qt framework) bulunan “signal ve slot” (sinyal ve yuva) mantığı, C# dilinde “Event ve Delegate” ikilisi ile gerçekleştirilir.
Bir YouTube kanalı düşünün. Kanal sahibi yeni bir video yüklediğinde (Olay gerçekleştiğinde/Yayınlandığında), o kanala “Abone olmuş (Subscribed)” herkese bildirim gider. Event mantığı tam olarak budur.
Neden Sadece Delegate Yerine Event Kullanıyoruz?
Delegate’ler tamamen açıktır ve güvensizdir. Bir sınıftaki public (açık) delegate değişkenini dışarıdan biri yanlışlıkla = operatörü ile tamamen ezebilir (diğer tüm aboneleri silebilir). Sıradan alanların (fields) aksine, olay (event) değişkenleri bir arayüzün (interface) parçası olabilirler; çünkü teknik olarak bu yapılar, çağrılacak temsilcileri eklemek (add) ve çıkarmak (remove) için kullanılan iki varsayılan (gizli) fonksiyondan oluşurlar.
Yani event anahtar kelimesini eklediğinizde, sınıf dışındaki bir kod o yapıya sadece abone olabilir (+=) veya abonelikten çıkabilir (-=). Olayı doğrudan dışarıdan tetikleyemez veya içini sıfırlayamaz.
Gerçek Hayat Senaryosu: E-Ticaret Sitesi Fiyat Düşüş Alarmı
Kullanıcıların bir e-ticaret sitesinde “Fiyat düşünce haber ver” butonuna bastığını düşünelim. Ürünün fiyatı düştüğünde, sisteme kayıtlı olan e-posta servisi veya SMS servisi otomatik olarak çalışmalıdır. Bu “Gevşek Bağlı” (Loosely coupled) yapıyı Event’lerle kuralım:
using System;
// 1. Temsilciyi Tanımlıyoruz
public delegate void FiyatDususTemsilcisi(string urunIsmi, decimal yeniFiyat);
public class Urun
{
public string Ad { get; set; }
private decimal _fiyat;
// 2. Temsilci tipinde bir Event (Olay) tanımlıyoruz
public event FiyatDususTemsilcisi FiyatDustugunde;
public Urun(string ad, decimal fiyat)
{
Ad = ad;
_fiyat = fiyat;
}
public decimal Fiyat
{
get { return _fiyat; }
set
{
// Eğer yeni fiyat eskisinden düşükse ve bu olaya ABONE olan birileri varsa (!= null)
if (value < _fiyat)
{
_fiyat = value;
// 3. Olayı Tetikliyoruz (Yayınlıyoruz - Fire/Invoke)
if (FiyatDustugunde != null)
{
FiyatDustugunde(Ad, _fiyat);
}
}
else
{
_fiyat = value;
}
}
}
}
class Program
{
// Abone olacak (çalışacak) metot 1
static void MusteriyeEmailGonder(string urun, decimal fiyat)
{
Console.WriteLine($"[EMAIL] Harika Haber! {urun} fiyatı {fiyat} TL'ye düştü!");
}
// Abone olacak (çalışacak) metot 2
static void MusteriyeSmsGonder(string urun, decimal fiyat)
{
Console.WriteLine($"[SMS] Fiyat Alarmı: {urun} artık {fiyat} TL!");
}
static void Main()
{
// Yeni bir ürün oluşturuyoruz
Urun laptop = new Urun("Gamer Laptop", 25000m);
// 4. Olaylara Abone Oluyoruz (Subscribe)
laptop.FiyatDustugunde += MusteriyeEmailGonder;
laptop.FiyatDustugunde += MusteriyeSmsGonder;
Console.WriteLine("Ürüne zam yapılıyor...");
laptop.Fiyat = 26000m; // Fiyat arttığı için hiçbir olay tetiklenmez.
Console.WriteLine("Ürüne büyük indirim yapılıyor...");
laptop.Fiyat = 19999m;
// İndirim yapıldığı an, Property (Özellik) içindeki kod çalışacak
// ve Event üzerinden hem Email hem de SMS metotlarını otomatik tetikleyecektir!
}
}
3. Modern C#’ta Hazır Temsilciler: Action, Func ve Predicate
Geçmişte C# geliştiricileri yukarıdaki örneklerdeki gibi her işlem için (public delegate void FiyatDususTemsilcisi...) uzun uzun özel temsilciler (custom delegates) tanımlamak zorundaydı. Ancak modern C# ve .NET ekosistemi, işleri hızlandırmak için bize hazır ve jenerik (generic) delegate tipleri sunar:
Action: Geriye hiçbir şey döndürmeyen (void) metotları temsil eder. Dilerseniz içine 16 taneye kadar parametre (Action<string, int>) verebilirsiniz.Func: Geriye mutlaka bir değer döndüren metotları temsil eder. ÖrneğinFunc<int, int, string>yazarsanız; dışarıdan iki adet tam sayı alan ve geriye metin (string) döndüren bir metodu temsil eder.Predicate: Geriye sadecebool(true veya false) döndüren metotları temsil eder. Genellikle veri listeleri içinde filtreleme/arama yaparken kullanılır.
Gerçek dünyada public event Action<string, decimal> FiyatDustugunde; şeklinde tanımlama yaparak kendi delegate’inizi yazma adımından tamamen kurtulabilirsiniz!
Sonuç Olarak
Delegates (Temsilciler) ve Events (Olaylar), özellikle modüler, bakımı kolay ve birbirinden habersiz (loosely coupled) sistemler yazmanın bel kemiğidir. C# dilinde sınıflar ve arayüzler arasındaki haberleşme yükünü bu yapılar üstlenir. İster buton tıklamaları, ister gelişmiş loglama (Serilog vb. kütüphaneler ile), isterse oyun motorlarındaki (Unity) tetikleyiciler olsun; arkada her zaman bu iki güçlü kavram çalışır.
İlk başlarda bu konsept size biraz karmaşık gelebilir. Ancak kodunuzu yukarıdaki “E-Ticaret Sitesi Fiyat Alarmı” örneğimizde olduğu gibi kurguladığınızda, spagetti koda dönüşmeden ne kadar esnek projeler üretebildiğinize siz de şaşıracaksınız. Bir sonraki dersimizde, C# 14 ile birlikte gelen yeniliklere ve modern .NET mimarilerine dalış yapacağız. İyi kodlamalar dileriz!




