Event memungkinkan sebuah class mengumumkan bahwa sesuatu terjadi tanpa tahu siapa yang mendengarkan. Object lain melakukan subscribe dengan +=, dan saat class itu memunculkan event-nya, method milik setiap subscriber dijalankan.
Output:
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 tidak tahu bahwa struk atau hadiah itu ada. Class ini memunculkan OrderPlaced, dan apa pun yang sudah subscribe akan bereaksi. Menambahkan reaksi baru (poin loyalitas, analitik) berarti menambahkan subscriber, bukan mengubah PlaceOrder.
Mendeklarasikan event
Deklarasi event adalah member bertipe delegate dengan keyword event:
public event EventHandler<OrderPlacedEventArgs> OrderPlaced;
Tipe delegate-nya menentukan signature handler. Kode .NET hampir selalu memakai salah satu dari dua tipe bawaan, dan mengikuti konvensi ini membuat event-mu terasa familier bagi setiap developer .NET lain:
EventHandler:void (object sender, EventArgs e), untuk event yang tidak membawa data. Munculkan denganEventArgs.Empty.EventHandler<TEventArgs>:void (object sender, TEventArgs e), denganTEventArgsadalah class milikmu yang membawa datanya.
Konvensi di sekitarnya:
senderadalah object yang memunculkan event, biasanyathis.- Class datanya diberi nama
{Something}EventArgs, diturunkan dariEventArgs(opsional sejak .NET 4.5, tetapi masih lazim), dan mengekspos property read-only. - Event diberi nama sesuai apa yang terjadi:
OrderPlaced,Closed,PriceChanged. Nama berakhiran...ing(Closing) dipakai untuk event yang dimunculkan sebelum aksinya, sering kali dengan cara untuk membatalkannya. - Di class yang dimaksudkan untuk diwarisi, event dimunculkan lewat method
protected virtual void OnOrderPlaced(OrderPlacedEventArgs e), supaya class turunan bisa ikut campur.
Subscribe dan unsubscribe
+= menambahkan handler dan -= menghapusnya. Handler bisa berupa method atau lambda. Untuk menghapus lambda nanti, simpan lambda itu di variabel, karena menulis lambda yang sama lagi menghasilkan delegate berbeda yang tidak akan ditemukan oleh -=:
Output:
set to 32
display shows 32 C
alarm: too hot
set to 35
display shows 35 C
set to 20
display shows 20 C
Menghapus handler yang tidak pernah ditambahkan bukan error; hal itu tidak berbuat apa-apa. EventHandler<int> menunjukkan bahwa argumen tipenya tidak harus diturunkan dari EventArgs di .NET modern, meskipun class khusus memberi ruang untuk menambahkan field nanti tanpa merusak subscriber.
Handler berjalan secara sinkron, sesuai urutan subscribe, di thread yang memunculkan event. Kalau satu handler melempar exception, handler sisanya tidak dijalankan dan exception-nya diteruskan ke kode yang memunculkan event.
Memunculkan event dengan aman
Event tanpa subscriber berisi null. Memanggilnya secara langsung akan melempar NullReferenceException, jadi munculkan dengan operator null-conditional:
OrderPlaced?.Invoke(this, args);
Sebelum C# 6, polanya adalah menyalin field ke variabel lokal, mengeceknya, lalu memanggil salinannya. Salinan itu penting di kode multithread: mengecek OrderPlaced != null lalu memanggil OrderPlaced(...) membaca field-nya dua kali, dan thread lain bisa menghapus handler terakhir di antaranya. ?. membacanya sekali, jadi lebih singkat sekaligus benar.
Kenapa event, bukan field delegate public
Tanpa keyword event, field delegate public tetap bisa dipakai untuk subscribe, tetapi memberi setiap subscriber kendali penuh:
Output:
analytics ping
analytics ping
Satu = yang diketik alih-alih += diam-diam menghapus dua handler sebelumnya, dan kode dari luar bisa memunculkan "klik" tanpa ada klik sama sekali. Menandai field-nya dengan event membuat kedua baris itu menjadi error compile:
error CS0070: The event 'Button.Clicked' can only appear on the left hand side of += or -= (except when used from within the type 'Button')
Di dalam Button, event itu tetap berperilaku seperti field delegate biasa, jadi class itu bisa memanggilnya dan mengecek apakah nilainya null.
Leak karena lupa unsubscribe
Saat kamu subscribe, event menyimpan sebuah delegate, dan delegate itu menyimpan reference ke object subscriber. Selama publisher masih hidup dan handler-nya masih terpasang, subscriber tidak bisa dibersihkan garbage collector, dan terus menerima event setelah kamu selesai memakainya:
Output:
closed widget shows 101.5
open widget shows 101.5
subscribers left: 1
closed widget shows 99.0
Mengisi leaky dengan null tidak berpengaruh apa-apa bagi feed: daftar handler-nya masih mereferensikan widget itu, jadi widget yang "ditutup" terus diperbarui dan tetap ada di memori. Widget di dalam blok using melakukan unsubscribe di Dispose dan berhenti menerima event. Ini salah satu memory leak paling umum di kode desktop dan server .NET, terutama dengan event static dan service yang dipakai di seluruh aplikasi, yang hidup sampai proses berakhir.
Aturannya: siapa pun yang subscribe ke event milik object yang umurnya lebih panjang darinya harus unsubscribe, biasanya di Dispose (lihat pernyataan using). Saat publisher dan subscriber punya umur yang sama, seperti sebuah form dan tombol-tombolnya sendiri, unsubscribe tidak diperlukan.
Accessor add dan remove buatan sendiri
Sebuah event bisa menentukan apa yang dilakukan += dan -=, seperti get dan set pada property. Hal ini jarang dipakai, tetapi begitulah cara framework menyimpan handler di dictionary bersama atau meneruskannya ke object lain:
private EventHandler closed;
public event EventHandler Closed
{
add { Console.WriteLine("subscriber added"); closed += value; }
remove { Console.WriteLine("subscriber removed"); closed -= value; }
}
Dengan accessor buatan sendiri, class memunculkan event lewat backing field-nya (closed?.Invoke(this, EventArgs.Empty)), karena event itu sendiri tidak lagi punya penyimpanan.
Pertanyaan yang sering diajukan
Apa itu event di C#?
Event adalah member class yang memungkinkan object lain meminta diberi tahu saat sesuatu terjadi. Event didukung oleh multicast delegate, tetapi kode dari luar hanya bisa menambahkan handler (+=) dan menghapusnya (-=). Hanya class yang mendeklarasikan event yang bisa memunculkannya.
Apa bedanya event dan delegate di C#?
Delegate adalah tipe yang mereferensikan method. Event adalah member yang dideklarasikan dengan tipe delegate ditambah keyword event, yang membatasi aksesnya: dari luar class kamu tidak bisa memanggilnya, membaca daftar handler-nya, atau meng-assign-nya dengan =, hanya subscribe dan unsubscribe. Field delegate public mengizinkan semua itu, jadi subscriber mana pun bisa menghapus subscriber lain atau memicu event-nya.
Bagaimana cara mengirim data lewat event C#?
Turunkan sebuah class dari EventArgs dengan datanya sebagai property read-only (public class OrderPlacedEventArgs : EventArgs { public decimal Total { get; } }) dan deklarasikan event-nya sebagai EventHandler<OrderPlacedEventArgs>. Munculkan event itu dengan OrderPlaced?.Invoke(this, new OrderPlacedEventArgs(total));.
Bagaimana cara memunculkan event dengan aman di C#?
Pakai MyEvent?.Invoke(this, args);. Event tanpa subscriber bernilai null, jadi memanggilnya secara langsung melempar NullReferenceException. Operator ?. membaca field-nya sekali, yang juga menghindari race condition saat thread lain menghapus handler terakhir di antara pengecekan null dan pemanggilannya.
Apakah event C# bisa menyebabkan memory leak?
Bisa. Subscribe menyimpan delegate yang mereferensikan subscriber, jadi publisher membuat subscriber tetap hidup selama publisher itu hidup. Publisher yang berumur panjang (event static, service yang dipakai di seluruh aplikasi) dengan subscriber berumur pendek yang tidak pernah unsubscribe akan menahan setiap subscriber itu di memori. Lakukan unsubscribe dengan -= saat subscriber selesai dipakai, biasanya di Dispose.