Delegate, bir metot imzasını temsil eden bir tiptir. Bir delegate tipindeki değişken o imzaya sahip bir metoda referans tutar ve değişkeni çağırmak metodu çağırır. C# fonksiyonları değer olarak böyle ele alır: onları saklayabilir, başka metotlara geçirebilir ve sonradan çağırabilirsiniz.
Çıktı:
40
70
68.00
delegate decimal PriceRule(decimal price);, class'ın yaptığı gibi yeni bir tip bildirir. Bir decimal alıp bir decimal döndüren her metot ona uyar. rule değişkeni sırayla üç farklı metodu gösterdi ve her çağrı o anda tuttuğu metodu çalıştırdı.
Bildirmek, Oluşturmak ve Çağırmak
Bir delegate tipi bildirimi, önünde delegate anahtar kelimesi olan bir metot imzasına benzer. Genellikle namespace seviyesinde sınıfların yanında ya da bir sınıfın içinde iç içe durur.
Bir delegate örneği oluşturmanın dört yolu vardır:
PriceRule a = HalfPrice; // method group (most common)
PriceRule b = new PriceRule(HalfPrice); // explicit constructor, same thing
PriceRule c = p => p - 5m; // lambda expression
PriceRule d = delegate (decimal p) { return p; }; // anonymous method (C# 2 syntax)
Ve onu çağırmanın aynı olan iki yolu: rule(80m) ve rule.Invoke(80m). Derleyici imzayı atama sırasında kontrol eder, bu yüzden uyumsuz bir metot çalışma zamanında bir sürpriz değil, bir derleme hatasıdır.
Bir delegate bir örnek metodunu da gösterebilir. O zaman hem metodu hem de onu çağıracağı nesneyi hatırlar:
Çıktı:
Ana got: Your order shipped
target: Mailbox, method: Receive
Target, delegate'in metodu üzerinde çağıracağı nesnedir (statik bir metot için null) ve Method metodu tanımlar. Hedefi tutmak aynı zamanda bir delegate'in o nesneyi canlı tuttuğu anlamına gelir; bu event'ler için önemlidir.
Callback Olarak Delegeler
Bir delegate'in asıl işi, bir metodun, çağıranının seçtiği kodu çağırmasını sağlamaktır. Metot bir şeyin ne zaman olacağını, çağıran ne olacağını tanımlar:
Çıktı:
[## ] 25%
[##### ] 50%
[####### ] 75%
[##########] 100%
import finished
ImportRows konsollar ya da çubuklar hakkında hiçbir şey bilmez. Aynı metot, çağıranın ne geçirdiğine bağlı olarak bir ilerleme çubuğunu, bir log satırını ya da bir arayüz güncellemesini yönetebilir. List.Sort(Comparison<T>), Array.Find(Predicate<T>), Task.Run(Action) ve her LINQ operatörü bu kalıbı izler.
Multicast Delegeler: += ve -=
Bir delegate birden fazla metot tutabilir. += çağrı listesine bir metot ekler, -= onu çıkarır ve delegate'i çağırmak her metodu sırayla çağırır:
Çıktı:
email sent for A-1001
stock updated for A-1001
warehouse notified for A-1001
3 handlers
email sent for A-1002
warehouse notified for A-1002
Multicast delegelerin üç ayrıntısı uygulamada önemlidir:
- Dönüş değerleri: delegate tipi bir değer döndürüyorsa multicast bir delegate'i çağırmak yalnızca son metodun sonucunu döndürür. Tüm sonuçları toplamak için
GetInvocationList()üzerinde dönüp her birini kendiniz çağırın. - İstisnalar: bir metot istisna fırlatırsa kalan metotlar çalışmaz ve istisna çağırana ulaşır.
- Değiştirilemezlik: delegeler değiştirilemez.
+=daha uzun bir listeye sahip yeni bir delegate oluşturur ve onu değişkene geri atar; bir kopya üzerinde-=ile çıkarmanın orijinali etkilememesinin nedeni budur.
Bir lambda'yı -= ile çıkarmak yalnızca aynı delegate örneğini verirseniz çalışır. Aynı lambda'yı iki kez yazmak iki farklı delegate oluşturur, bu yüzden handlers -= id => Log(id); hiçbir şey çıkarmaz. Aboneliğini iptal etmeyi planladığınız lambda'yı bir değişkende saklayın.
Null Delegeler ve ?.Invoke
Hiçbir şey atanmamış bir delegate değişkeni null'dır ve son metodu -= ile çıkarmak da null bırakır. null'ı çağırmak NullReferenceException fırlatır:
Çıktı:
no handler, no crash
saved draft.txt
NullReferenceException
?.Invoke, isteğe bağlı bir callback'i çağırmanın standart yoludur. ?. operatörü doğrudan parantezlerin önüne konamaz (onSaved?("x") geçerli bir sözdizimi değildir), bu yüzden kalıp delegate'in Invoke metodunu adıyla çağırır; bu, kısa çağrıyla aynı işi yapar.
Delegeler, Func, Action ve Event'ler
Her imza için bir delegate tipi bildirmek yorucu olurdu, bu yüzden .NET generic olanlarla gelir:
| Yerleşik tip | Eşdeğer özel bildirim |
|---|---|
Action | delegate void Action(); |
Action<T> | delegate void Action<T>(T arg); |
Func<TResult> | delegate TResult Func<TResult>(); |
Func<T, TResult> | delegate TResult Func<T, TResult>(T arg); |
Predicate<T> | delegate bool Predicate<T>(T obj); |
İlk örnekteki PriceRule, Func<decimal, decimal> ile aynı şekle sahiptir, ama ikisi ayrı tiplerdir ve birbirine dönüşmez. İmzaları aynı olan iki yerleşik tip bile dönüşmez:
Func<int, bool> isPositive = x => x > 0;
Predicate<int> p = isPositive;
// error CS0029: Cannot implicitly convert type 'System.Func<int, bool>' to 'System.Predicate<int>'
Predicate<int> ok = new Predicate<int>(isPositive); // wrap it explicitly
Predicate<int> ok2 = x => isPositive(x); // or with a lambda
Bir lambda uyumlu her delegate tipine dönüşür, bu yüzden API'ler genellikle lambda alır ve bu soru hiç gündeme gelmez. Func ve Action sayfası generic aileyi ve özel bir bildirimin ne zaman hâlâ değerli olduğunu ele alır.
Event'ler doğrudan delegeler üzerine kuruludur. Bir event alanı, dışarıdaki kodun yalnızca += ve -= yapabildiği, asla çağıramadığı ya da üzerine yazamadığı multicast bir delegate'tir. Yukarıdaki multicast örneğindeki abone listesi, bir event'in bu ek korumayla sizin için yönettiği şeyin tam kendisidir. Event'ler kendi sayfalarında ele alınır.
Sıkça Sorulan Sorular
C#'ta delegate nedir?
Delegate, bir metoda tip güvenli bir referanstır. delegate decimal PriceRule(decimal price); gibi bir imzayla bir delegate tipi bildirirsiniz; o imzaya sahip her metot (statik, örnek ya da bir lambda) o tipte bir değişkende saklanabilir ve sonradan onun üzerinden çağrılabilir. C# fonksiyonları değer olarak delegelerle geçirir.
C#'ta delegate ile Func arasındaki fark nedir?
Func<T, TResult> ve Action<T>, .NET'in sizin için zaten bildirdiği, parametre ve dönüş tipleri üzerinde generic olan delegate tipleridir. Özel bir delegate bildirimi tipe rolünü belgeleyen bir ad ve parametre adları verir ve ref, out ve in parametrelerini destekler. Çoğu kod için Func ve Action yeterlidir.
C#'ta multicast delegate nedir?
Birkaç metot tutan bir delegate'tir. += çağrı listesine bir metot ekler, -= bir tane çıkarır; delegate'i çağırmak hepsini sırayla çağırır. Delegate bir değer döndürüyorsa çağıran yalnızca son metodun sonucunu alır ve bir metottaki istisna geri kalanların çalışmasını durdurur.
null olabilecek bir delegate güvenle nasıl çağrılır?
Null koşullu operatörü kullanın: onProgress?.Invoke(50);. Hiç metot atanmamış bir delegate değişkeni null'dır ve onu doğrudan çağırmak NullReferenceException fırlatır. ?.Invoke değişkeni bir kez okur, bu yüzden aynı anda başka bir thread son handler'ı çıkarsa da güvenlidir.
Func ya da Action yerine ne zaman kendi delegate tipimi bildirmeliyim?
İmza ref, out ya da in parametreleri gerektirdiğinde (Func ve Action bunları ifade edemez), adlandırılmış bir tip public bir API'yi daha anlaşılır kıldığında (delegate bool Validator(string input)) ya da parametre adlarının IntelliSense'te görünmesini istediğinizde. Aksi halde her .NET geliştiricisinin tanıdığı Func ve Action'ı tercih edin.