Menu

C# Event'ler: event Anahtar Kelimesi, EventHandler, Abone Olmak ve Tetiklemek

C#'ta event'ler nasıl çalışır: EventHandler ve EventHandler<T> ile bir event bildirmek, özel EventArgs sınıfları, += ve -= ile abone olmak ve aboneliği bırakmak, bir event'i ?.Invoke ile güvenle tetiklemek, bir event'in neden public bir delegate alanından iyi olduğu ve unutulmuş bir abonelik iptalinin yol açtığı bellek sızıntısı.

Bu sayfada çalıştırılabilir editörler var - düzenle, çalıştır ve sonucu anında gör.

Bir event, bir sınıfın kimin dinlediğini bilmeden bir şey olduğunu duyurmasını sağlar. Diğer nesneler += ile abone olur ve sınıf event'i tetiklediğinde her abonenin metodu çalışır.

Çıktı:

Order A-17 saved
  receipt emailed for A-17
Order A-18 saved
  receipt emailed for A-18
  free gift added to A-18

Shop, makbuzların ya da hediyelerin var olduğunu bilmez. OrderPlaced'i tetikler ve abone olan her şey tepki verir. Yeni bir tepki (sadakat puanları, analitik) eklemek PlaceOrder'ı düzenlemek değil, bir abone eklemek demektir.

Bir Event Bildirmek

Bir event bildirimi, event anahtar kelimesine sahip, delegate tipli bir üyedir:

public event EventHandler<OrderPlacedEventArgs> OrderPlaced;

Delegate tipi handler imzasını sabitler. .NET kodu neredeyse her zaman iki yerleşik tipten birini kullanır ve bu geleneği izlemek event'lerinizi diğer her .NET geliştiricisi için tanıdık kılar:

  • EventHandler: veri taşımayan event'ler için void (object sender, EventArgs e). Onu EventArgs.Empty ile tetikleyin.
  • EventHandler<TEventArgs>: void (object sender, TEventArgs e); burada TEventArgs veriyi taşıyan sınıfınızdır.

Bunların etrafındaki gelenekler:

  • sender, event'i tetikleyen nesnedir, genellikle this.
  • Veri sınıfı {Something}EventArgs olarak adlandırılır, EventArgs'tan türer (.NET 4.5'ten beri isteğe bağlı, yine de alışılmış) ve salt okunur property'ler sunar.
  • Event olan şeye göre adlandırılır: OrderPlaced, Closed, PriceChanged. ...ing bir ad (Closing), eylemden önce tetiklenen ve çoğu zaman iptal edilebilen event'ler için kullanılır.
  • Kalıtılmak üzere tasarlanmış sınıflarda tetikleme, türetilmiş sınıfların araya girebilmesi için protected virtual void OnOrderPlaced(OrderPlacedEventArgs e) metodu üzerinden yapılır.

Abone Olmak ve Aboneliği Bırakmak

+= bir handler ekler, -= onu çıkarır. Handler bir metot ya da bir lambda olabilir. Bir lambda'yı sonradan çıkarmak için onu bir değişkende tutun, çünkü aynı lambda'yı tekrar yazmak -='in bulamayacağı farklı bir delegate oluşturur:

Çıktı:

set to 32
  display shows 32 C
  alarm: too hot
set to 35
  display shows 35 C
set to 20
  display shows 20 C

Hiç eklenmemiş bir handler'ı çıkarmak hata değildir; hiçbir şey yapmaz. EventHandler<int>, modern .NET'te tip argümanının EventArgs'tan türemesi gerekmediğini gösterir, ama ayrı bir sınıf aboneleri bozmadan sonradan alan eklemeye yer bırakır.

Handler'lar, event'i tetikleyen thread'de, abonelik sırasıyla eşzamanlı olarak çalışır. Bir handler istisna fırlatırsa kalan handler'lar çalışmaz ve istisna event'i tetikleyen koda yayılır.

Bir Event'i Güvenle Tetiklemek

Abonesi olmayan bir event null tutar. Onu doğrudan çağırmak NullReferenceException fırlatırdı, bu yüzden null koşullu operatörle tetikleyin:

OrderPlaced?.Invoke(this, args);

C# 6'dan önce kalıp, alanı yerel bir değişkene kopyalamak, kontrol etmek ve kopyayı çağırmaktı. Kopya çok thread'li kodda önemlidir: OrderPlaced != null kontrol edip sonra OrderPlaced(...) çağırmak alanı iki kez okur ve arada başka bir thread son handler'ı çıkarabilir. ?. onu bir kez okur, bu yüzden hem daha kısa hem doğrudur.

Neden Public Bir Delegate Alanı Değil de event

event anahtar kelimesi olmadan public bir delegate alanı abone olmak için çalışır, ama her aboneye tam kontrol verir:

Çıktı:

analytics ping
analytics ping

+= yerine yazılan tek bir = önceki iki handler'ı sessizce kaldırdı ve dışarıdaki kod hiçbir tıklama olmadan "tıklama"yı tetikleyebildi. Alanı event olarak işaretlemek iki satırı da derleme hatası yapar:

error CS0070: The event 'Button.Clicked' can only appear on the left hand side of += or -= (except when used from within the type 'Button')

Button'ın içinde event hâlâ normal bir delegate alanı gibi davranır, bu yüzden sınıf onu çağırabilir ve null için kontrol edebilir.

Unutulmuş Abonelik İptali Sızıntısı

Abone olduğunuzda event bir delegate saklar ve delegate abone nesneye bir referans tutar. Yayıncı yaşadığı ve handler bağlı olduğu sürece abone çöp toplayıcı tarafından toplanamaz ve onunla işiniz bittikten sonra da event almaya devam eder:

Çıktı:

  closed widget shows 101.5
  open widget shows 101.5
subscribers left: 1
  closed widget shows 99.0

leaky'yi null yapmak feed için hiçbir şey değiştirmedi: handler listesi hâlâ o widget'a başvuruyor, bu yüzden "kapatılmış" widget güncellenmeye ve bellekte kalmaya devam ediyor. using bloğundaki widget Dispose içinde aboneliğini bıraktı ve almayı durdurdu. Bu, .NET masaüstü ve sunucu kodundaki en yaygın bellek sızıntılarından biridir; özellikle süreç bitene kadar yaşayan statik event'ler ve uygulama genelindeki servislerle.

Kural: kendisinden uzun yaşayan bir nesnenin event'ine abone olan, aboneliği bırakmalıdır; genellikle Dispose içinde (bkz. using ifadesi). Yayıncı ve abone aynı ömre sahip olduğunda, örneğin bir form ve kendi butonları, abonelik iptali gerekmez.

Özel add ve remove Erişimcileri

Bir event, bir property'nin get ve set'i gibi, += ve -='in ne yapacağını tanımlayabilir. Nadirdir, ama framework'lerin handler'ları paylaşılan bir dictionary'de saklaması ya da başka bir nesneye iletmesi böyledir:

private EventHandler closed;

public event EventHandler Closed
{
    add    { Console.WriteLine("subscriber added");   closed += value; }
    remove { Console.WriteLine("subscriber removed"); closed -= value; }
}

Özel erişimcilerle sınıf event'i arka plan alanı üzerinden tetikler (closed?.Invoke(this, EventArgs.Empty)), çünkü event'in kendisinin artık depolaması yoktur.

Sıkça Sorulan Sorular

C#'ta event nedir?

Event, diğer nesnelerin bir şey olduğunda haberdar edilmek istemesini sağlayan bir sınıf üyesidir. Arkasında multicast bir delegate vardır, ama dışarıdaki kod yalnızca handler ekleyebilir (+=) ve çıkarabilir (-=). Event'i yalnızca onu bildiren sınıf tetikleyebilir.

C#'ta event ile delegate arasındaki fark nedir?

Delegate, metotlara başvuran bir tiptir. Event ise bir delegate tipi ve event anahtar kelimesiyle bildirilen, erişimi kısıtlayan bir üyedir: sınıfın dışından onu çağıramaz, handler listesini okuyamaz ya da = ile atayamazsınız, yalnızca abone olup aboneliği bırakabilirsiniz. Public bir delegate alanı bunların hepsine izin verir, bu yüzden herhangi bir abone diğerlerini silebilir ya da event'i tetikleyebilir.

Bir C# event'iyle nasıl veri geçirilir?

Veriyi salt okunur property'ler olarak içeren, EventArgs'tan türeyen bir sınıf oluşturun (public class OrderPlacedEventArgs : EventArgs { public decimal Total { get; } }) ve event'i EventHandler<OrderPlacedEventArgs> olarak bildirin. Onu OrderPlaced?.Invoke(this, new OrderPlacedEventArgs(total)); ile tetikleyin.

C#'ta bir event güvenle nasıl tetiklenir?

MyEvent?.Invoke(this, args); kullanın. Abonesi olmayan bir event null'dır, bu yüzden onu doğrudan çağırmak NullReferenceException fırlatır. ?. operatörü alanı bir kez okur; bu, başka bir thread'in bir null kontrolü ile çağrı arasında son handler'ı çıkardığı bir yarış durumundan da kaçınır.

C# event'leri bellek sızıntısına yol açabilir mi?

Evet. Abone olmak, aboneye başvuran bir delegate saklar, bu yüzden yayıncı yaşadığı sürece aboneyi de canlı tutar. Aboneliği hiç bırakmayan kısa ömürlü abonelere sahip uzun ömürlü bir yayıncı (statik bir event, uygulama genelinde bir servis) her birini bellekte tutar. Abonenin işi bittiğinde, genellikle Dispose içinde, -= ile aboneliği bırakın.

Coddy programming languages illustration

Coddy ile kodlamayı öğren

BAŞLA