C#'ta delegate, belirli bir metot imzasına uyan herhangi bir metodu işaret edebilen ve o metodu daha sonra çağırmanıza olanak tanıyan bir referans türüdür; event ise bu referansı dışarıdan kontrolsüzce değiştirilmeye karşı koruyan, delegate üzerine inşa edilmiş güvenli bir katmandır. Kısaca delegate "bir metodu taşıyabilen kutu", event ise "bu kutunun kapağını yalnızca belirli işlemlere açık tutan kilit" gibi düşünülebilir. Bu yazıda ikisi arasındaki farkı, ne zaman hangisinin kullanılacağını ve C#'ta olay yönetiminin günlük hayatta nasıl çalıştığını adım adım, kod örnekleriyle göreceksiniz.
Delegate Nedir? Fonksiyon İmzasına Uyan Referans Türü
C#'ta delegate, en yalın tanımıyla belirli bir metot imzasına uyan herhangi bir metodu işaret edebilen bir referans türüdür. Bir değişkenin bir sayıyı veya bir metnin bir kelimeyi tutması gibi, delegate de bir metodu tutar. Buradaki tek şart, işaret edilecek metodun parametre sayısı, parametre tipleri ve geriye dönüş tipi bakımından delegate'in tanımladığı "kalıba" uymasıdır.
Bunu günlük hayattan bir benzetmeyle düşünelim: bir posta kutusu hayal edin. Kutunun üzerinde belirli bir format vardır (örneğin belirli bir adres yapısı, belirli bir boyut sınırı). Bu formata uyan her mektup o kutuya girebilir; kutunun kime ait olduğu, içindeki mektubun kim tarafından yazıldığı önemli değildir, önemli olan mektubun kutunun kabul ettiği formata uymasıdır. Delegate de tıpkı böyle çalışır: hangi metodun ona atandığı değil, o metodun imzaya uyup uymadığı önemlidir. Bir başka benzetme de belirli bir formatta çalışan telefon numarası gibidir; numarayı kim arıyor olursa olsun, hattın diğer ucunda o numaraya uygun bir "cihaz" (metot) yanıt verir.
Peki bu neden işe yarar? Çünkü delegate sayesinde metotları tıpkı bir değişken gibi parametre olarak taşıyabilir, bir metoda başka bir metodu argüman olarak gönderebilir veya çalışma zamanında hangi metodun çalışacağına karar verebilirsiniz. Bu, özellikle bir işlemin "nasıl" yapılacağını değil "ne zaman ve hangi metotla" yapılacağını dışarıdan belirlemek istediğinizde büyük esneklik sağlar. Örneğin bir sıralama algoritmasına hangi kritere göre sıralama yapılacağını dışarıdan bir metotla bildirmek, ya da bir işlem bittiğinde hangi fonksiyonun tetikleneceğini önceden tanımlamak bu sayede mümkün olur. Bu esneklik, ileride göreceğimiz event mekanizmasının da temelini oluşturur.
Delegate Tanımlama, Örnekleme ve Çağırma: Adım Adım
Bir delegate'i hayata geçirmek, birbirini takip eden birkaç net adımdan oluşur. Bu adımları sırasıyla anlarsanız, delegate kavramı soyut bir tanım olmaktan çıkıp somut bir araç haline gelir.
- Delegate türünü tanımlayın: Önce hangi imzaya (parametre listesi ve dönüş tipi) sahip metotların kabul edileceğini belirten bir delegate türü tanımlarsınız. Bu, kutunun formatını belirlemek gibidir.
- İmzaya uyan bir metot yazın: Delegate'in beklediği parametre ve dönüş tipiyle birebir eşleşen sıradan bir metot yazarsınız; bu metot statik veya bir nesneye ait olabilir.
- Delegate örneği oluşturun: Tanımladığınız delegate türünden bir değişken oluşturursunuz; bu değişken henüz boş bir kutudur, içine bir metot koymanız gerekir.
- Metodu delegate'e atayın: İkinci adımda yazdığınız metodu bu değişkene atarsınız; artık delegate, o metoda giden bir referans taşımaktadır.
- Delegate'i çağırın: Delegate değişkenini normal bir metot gibi parantezle çağırdığınızda, aslında içine atanmış olan metot çalışır.
Bu beş adım kâğıt üzerinde basit görünse de, uygulamada kod yazarken imza uyuşmazlığı veya atama sırası gibi küçük hatalar sık yapılır. Bu adımları ne kadar iyi kavradığınızı görmek isterseniz, C# bilgi seviyeni ölçen kısa test ile kendinizi hızlıca sınayabilirsiniz.
Multicast Delegate: += ile Birden Fazla Metodu Zincirlemek

Delegate'in en güçlü yanlarından biri, tek bir metotla sınırlı kalmamasıdır. Bir delegate değişkenine += operatörü ile birden fazla metot ekleyebilirsiniz; buna multicast delegate denir. Yani aynı posta kutusuna, aynı formata uyan birden fazla mektubu art arda bırakabilirsiniz; delegate çağrıldığında bu mektupların hepsi sırayla "okunur", yani atanan tüm metotlar tek tek çalıştırılır.
Burada dikkat edilmesi gereken önemli bir nokta, çağrı sırasının kesinlikle ekleme sırasına göre işlemesidir. Bir metodu += ile önce eklerseniz, delegate çağrıldığında o metot diğerlerinden önce çalışır. Bu davranış, örneğin bir işlem tamamlandığında birden fazla loglama, bildirim veya arayüz güncelleme metodunu belirli bir sırayla tetiklemek istediğinizde oldukça kullanışlıdır.
Ancak multicast delegate kullanırken gözden kaçırılan bir detay vardır: eğer delegate türü geriye bir değer döndürüyorsa (yani dönüş tipi void değilse), zincire eklenen tüm metotlar sırayla çalışsa da, delegate çağrısının size geri döndürdüğü değer yalnızca zincirdeki son metodun sonucu olur. Ara metotların döndürdüğü değerler kaybolur, hiçbir yere kaydedilmez. Bu yüzden geriye değer bekleyen senaryolarda multicast delegate kullanmak yerine, genellikle tek metotlu delegate'ler veya sonuçları ayrı ayrı toplayan farklı bir yaklaşım tercih edilir. Bu ince fark, özellikle hata ayıklarken "neden sadece son metodun sonucunu görüyorum" sorusunun cevabını oluşturur ve delegate mantığını gerçekten kavramış olanla ezberleyeni birbirinden ayıran noktalardan biridir.
Action ve Func: Hazır Delegate Türleri Karşılaştırması
Her delegate türünü sıfırdan tanımlamak zorunda değilsiniz. C# içinde hazır olarak gelen Action ve Func delegate aileleri, günlük kodlamada en çok ihtiyaç duyulan iki senaryoyu karşılar: bir işlemi çalıştırıp hiçbir şey döndürmemek ya da bir işlemi çalıştırıp sonucu geri almak. Bu iki tür sayesinde her defasında yeni bir delegate tanımı yazmak yerine, doğrudan parametre listesine bakarak kullanıma geçebilirsiniz.
Action, adından da anlaşılacağı gibi bir "eylemi" temsil eder ve her zaman void döner; yani geriye herhangi bir değer beklemez. Func ise tam tersine mutlaka bir değer döndürür ve bunun kuralı basittir: tanımlanan tip parametrelerinin en sonuncusu, metodun dönüş tipini belirtir. Örneğin Func<int, int, int> ifadesinde ilk iki int parametre, son int ise dönüş değeridir. Bu küçük ama kritik fark, hangi durumda hangi türü seçeceğinizi belirleyen temel ölçüttür.
| Özellik | Action | Func |
|---|---|---|
| Geriye değer dönüp dönmemesi | Dönmez, her zaman void | Her zaman bir değer döndürür |
| Parametre sayısı | 0 ile 16 arası parametre alabilir | 0 ile 16 arası parametre alır, son tip dönüş değeridir |
| Tipik kullanım senaryosu | Loglama, bildirim gönderme, ekrana yazdırma gibi yan etkili işlemler | Hesaplama, filtreleme, dönüştürme gibi sonuç üreten işlemler |
Pratikte bir metodu Action olarak mı yoksa Func olarak mı tanımlayacağınıza karar verirken kendinize tek bir soru sormanız yeterlidir: "Bu işlemin sonucunda bana geri dönecek bir değer var mı?" Cevap hayırsa Action, evetse Func kullanmanız kodunuzu hem daha okunabilir hem de daha az hataya açık hale getirir.
event Anahtar Kelimesi Delegate'i Nasıl Güvenli Hale Getirir?

Bir önceki bölümlerde gördüğünüz saf delegate değişkenleri, tanımlandıkları sınıfın dışından da doğrudan erişilebilir ve değiştirilebilir durumdadır. Bu, küçük örneklerde sorun yaratmasa da büyüyen projelerde ciddi bir risk oluşturur: herhangi bir dış kod, = operatörüyle mevcut tüm abonelikleri silip kendi metodunu atayabilir ya da delegate'i doğrudan çağırarak sizin kontrolünüz dışında bir olayı tetikleyebilir. İşte tam bu noktada event anahtar kelimesi devreye girer ve delegate'in etrafına bir güvenlik duvarı örer.
Bir delegate alanını event olarak işaretlediğinizde, sınıf dışındaki kod artık ona = ile doğrudan yeni bir değer atayamaz ve onu parantezle çağırarak tetikleyemez. Dışarıdan yapılabilecek tek işlem, += ile abone olmak veya -= ile abonelikten çıkmaktır. Olayı fiilen tetikleme yetkisi yalnızca olayı tanımlayan sınıfın kendisine aittir. Bu kısıtlama rastgele bir kural değil, doğrudan nesne yönelimli programlamanın temel ilkesi olan kapsülleme mantığının olaylara uygulanmış halidir.
Bu yapı, yazılımda çok yaygın kullanılan yayıncı-abone (publisher-subscriber) modelini mümkün kılar: bir sınıf "yayıncı" rolünü üstlenip belirli bir durum oluştuğunda olayı tetikler, diğer sınıflar ise "abone" olarak bu olaya kayıt olur ve olay gerçekleştiğinde otomatik olarak haberdar edilir. Yayıncı, abonelerin kim olduğunu veya kaç tane olduğunu bilmek zorunda değildir; sadece olayı tetiklemekten sorumludur. Bu ayrım, sınıflar arasındaki bağımlılığı azaltır ve kodun farklı parçalarının birbirinden habersiz, gevşek bağlı biçimde çalışmasını sağlar.
Delegate ve event arasındaki bu ince ama önemli farkı gerçek projelerde uygulamalı olarak deneyimlemek, kavramı kalıcı hale getirmenin en etkili yoludur. Nesne yönelimli programlamanın kapsülleme, yayıncı-abone ilişkisi ve olay tabanlı tasarım gibi konularını bir eğitmen eşliğinde, canlı örneklerle pekiştirmek isteyenler için canlı yazılım eğitimi programları adım adım ilerleyen bir öğrenme ortamı sunar.
Delegate mi, Event mi? Pratik Karar Tablosu
Delegate ve event'in ne işe yaradığını anladıktan sonra akla gelen doğal soru şudur: "Peki ben hangi durumda hangisini kullanmalıyım?" Bu sorunun cevabı çoğu zaman kodun kimin tarafından tetikleneceğine ve hangi ilişkiyi kurmak istediğinize bağlıdır. Aşağıdaki tablo, günlük kod yazarken karşınıza çıkabilecek tipik kriterleri ve bu kriterlere göre hangi yapıyı tercih etmeniz gerektiğini özetler.
| Durum / Kriter | Delegate Kullan | Event Kullan |
|---|---|---|
| Dışarıdan tetikleme gerekiyor mu? | Evet, çağıran kod delegate'i doğrudan tetikleyebilmeli | Hayır, tetikleme yetkisi yalnızca tanımlayan sınıfta kalmalı |
| Geri dönüş değeri lazım mı? | Evet, sonucun bir değere ihtiyacı var (örneğin Func ile) | Genellikle hayır, event'ler çoğunlukla void imzalı bildirimlerdir |
| Yayıncı-abone ilişkisi mi kurulacak? | Hayır, tek yönlü bir metot referansı yeterli | Evet, birden fazla bileşenin bir duruma bağımsızca abone olması gerekiyor |
Kısaca özetlemek gerekirse: eğer amacınız bir metodu parametre gibi taşımak, bir sonucu geri almak ya da esnek bir fonksiyon referansı kullanmaksa saf delegate yeterlidir. Ama amacınız bir durumun gerçekleştiğini ilgili taraflara güvenli bir şekilde duyurmak ve bu duyuruyu yalnızca ilgili sınıfın kontrolünde tutmaksa, event anahtar kelimesiyle sarmalanmış bir delegate kullanmak, kodunuzu hem daha güvenli hem de daha okunabilir kılar.
Kod Üzerinde Görelim: Basit OnClick Olayı Simülasyonu
Şimdiye kadar anlatılanları tek bir örnekte birleştirelim. Aşağıdaki kod, WPF veya WinForms gibi bir arayüz kütüphanesi olmadan, sade bir C# sınıfı üzerinden bir "tıklama" olayının nasıl tanımlanıp tetiklendiğini gösteriyor. Örnekte hem delegate temelli event tanımı hem de += ile yapılan multicast abonelik bir arada yer alıyor:
using System;
public class Button
{
public event EventHandler OnClick;
public void RaiseClick()
{
OnClick?.Invoke(this, EventArgs.Empty);
}
}
class Program
{
static void LogClick(object sender, EventArgs e)
=> Console.WriteLine("Log: Tıklama algılandı.");
static void ShowMessage(object sender, EventArgs e)
=> Console.WriteLine("Mesaj: Buton tıklandı!");
static void Main()
{
Button myButton = new Button();
myButton.OnClick += LogClick;
myButton.OnClick += ShowMessage;
myButton.RaiseClick();
}
}
Burada Button sınıfı, kendi içinde EventHandler tipinde bir OnClick olayı taşıyor. Main metodu içinde bu olaya iki farklı metot (LogClick ve ShowMessage) += ile abone oluyor; bu, önceki bölümde anlatılan multicast delegate mantığının event üzerinde uygulanmış hali. RaiseClick çağrıldığında, olaya abone olan tüm metotlar sırayla çalışıyor ve konsola iki farklı satır yazdırıyor. Dikkat edilmesi gereken kritik nokta şu: olayı tetikleyen kod (RaiseClick) Button sınıfının kendi içinde tanımlı; dışarıdan sadece abone olunabiliyor, doğrudan tetiklenemiyor. Bu ayrım, event'in neden delegate'e göre daha güvenli bir yapı sunduğunu somut olarak gösteriyor.
Bu tür senaryoları kendi projelerinizde denemek, konuyu kalıcı hale getirmenin en etkili yolu. Kavramı tek başına ilerlerken oturtmakta zorlanıyorsanız, 1-1 özel ders desteğiyle bu örnekleri adım adım birlikte kurgulamak süreci hızlandırabilir.
Sık Yapılan Hatalar ve Dikkat Edilmesi Gerekenler
Delegate ve event ile çalışırken yeni başlayanların sıkça takıldığı bazı noktalar var. Bunların çoğu, C# derleyicisinin bilinçli olarak koyduğu kısıtlamalardan kaynaklanıyor ve aslında sizi hatalardan koruyor.
İlk ve en yaygın soru şu: neden myButton.OnClick(this, EventArgs.Empty); gibi bir satır Program sınıfı içinde yazılırsa derleme hatası alınır? Çünkü event anahtar kelimesi, delegate alanını dışarıdan yalnızca += ve -= ile abone olunabilecek şekilde sınırlar; olayı tetikleme (invoke) yetkisi yalnızca onu tanımlayan sınıfın kendisine aittir. Bu, önceki bölümde bahsedilen "kapsülleme" ilkesinin doğrudan uygulamasıdır ve bir sınıfın kendi iç durumunu dış müdahaleden koruma amacı taşır.
İkinci kritik nokta ise null kontrolüdür. Bir event'e hiç kimse abone olmadıysa, o event'in değeri null'dur. Doğrudan OnClick(this, EventArgs.Empty); şeklinde çağırmak bu durumda NullReferenceException fırlatır. Bunu önlemenin standart yolu, örnekteki gibi null-conditional operatörü kullanmaktır:
OnClick?.Invoke(this, EventArgs.Empty);
Bu tek satır, event'in abonesi olup olmadığını kontrol edip yalnızca abone varsa çağrıyı yapar; aksi halde sessizce hiçbir şey yapmadan devam eder.
Bunların dışında sık karşılaşılan diğer hatalar şöyle sıralanabilir:
- Delegate ile event kavramlarını birbirinin yerine kullanmaya çalışmak; oysa delegate genel amaçlı bir fonksiyon referansıyken event, delegate'i belirli bir kullanım senaryosuna (bildirim/olay) göre kısıtlayan özel bir sarmalayıcıdır.
- Gerek olmadığı halde her metodu multicast zincirine eklemek; bu, hangi metodun ne zaman çalıştığını takip etmeyi zorlaştırır ve hata ayıklamayı karmaşıklaştırır.
- Abone olunan bir metodu iş bitince
-=ile çıkarmayı unutmak; bu durum, özellikle uzun ömürlü nesnelerde gereksiz referansların bellekte kalmasına (memory leak) yol açabilir. - Event imzası için
Action,Funcveya özel bir delegate arasında seçim yaparken tutarsız davranmak; bir projede aynı tür olaylar için farklı imza standartları kullanmak kod okunabilirliğini düşürür.
Bu tür ayrıntıları teoride okumak ile kendi kodunuzda deneyerek görmek arasında büyük fark var. Konuyla ilgili daha fazla pratik örnek ve C# ile Java/Python karşılaştırmaları için yazılım eğitimi blog yazıları arasında ilerleyerek konuyu farklı açılardan pekiştirebilirsiniz.
Sık Sorulan Sorular
Delegate nedir, en basit haliyle nasıl açıklanır?
Delegate, belirli bir imzaya (parametre tipleri ve dönüş tipi) uyan metotları işaret edebilen bir referans türüdür. Bir telefon numarası gibi düşünülebilir: numaranın kendisi bir kişi değildir ama o kişiye ulaşmayı sağlar; delegate de metodun kendisi değildir ama o metoda ulaşmayı ve onu çağırmayı sağlar.
Event, delegate'ten tam olarak nasıl farklıdır?
Event, arka planda yine bir delegate kullanır ancak bu delegate'in dışarıdan yalnızca abone olunabilir (+=, -=) hale gelmesini sağlar; doğrudan tetikleme yetkisi yalnızca olayı tanımlayan sınıfa aittir. Bu sayede delegate'in esnekliği korunurken, olayın kontrolsüz biçimde dışarıdan çalıştırılmasının önüne geçilir.
Action ile Func arasındaki temel fark nedir?
Action, geriye değer döndürmeyen (void) metotları temsil eden hazır delegate türüdür; Func ise son tip parametresi dönüş tipi olacak şekilde geriye bir değer döndüren metotları temsil eder. İkisi de kendi delegate türünüzü tanımlama zahmetinden kurtarır.
Multicast delegate ne zaman kullanılır?
Aynı olaya veya işleme birden fazla metodun tepki vermesi gerektiğinde, örneğin bir işlem tamamlandığında hem loglama hem bildirim gösterme gibi birden fazla bağımsız adımın aynı anda tetiklenmesi istendiğinde multicast delegate kullanılır.
Bir event neden sınıf dışından doğrudan tetiklenemez?
event anahtar kelimesi, delegate alanına erişimi kapsülleme kuralıyla sınırlar; dış kod yalnızca abone olabilir veya abonelikten çıkabilir, tetikleme yetkisi tasarım gereği yalnızca olayı tanımlayan sınıfın kendi metotlarında kalır.
Delegate ve event kavramları yalnızca C#'a mı özgüdür?
Delegate ve event terimleri C#'a özgü isimlendirmeler olsa da, "bir fonksiyona referans tutma" ve "olay tabanlı programlama" fikri birçok dilde farklı isimlerle karşımıza çıkar; bu nedenle mantığı kavramak, farklı diller arasında geçiş yaparken de işe yarar.
Delegate ve event öğrenmeden önce hangi konulara hakim olmak gerekir?
Metot tanımlama, parametre ve dönüş tipi kavramları, sınıf ve nesne mantığı ile temel kapsülleme (public/private erişim belirleyicileri) konularında rahat olmak, delegate ve event'i anlamlandırmayı önemli ölçüde kolaylaştırır.
Delegate ve event, C#'ın nesne yönelimli dünyasında olay tabanlı düşünmeyi öğreten temel yapı taşlarındandır ve bir kez mantığı oturduğunda ileride karşılaşacağınız birçok kütüphanede aynı deseni tanıdık bulursunuz. Bu konuları kendi başınıza pekiştirirken takıldığınız noktalar olursa, 1-1 özel Java kursu kapsamında benzer nesne yönelimli kavramları farklı bir dilde karşılaştırmalı olarak da görebilirsiniz.