Event adalah anggota kelas yang membungkus delegate dengan aturan akses lebih ketat, yaitu delegate hanya boleh dipicu oleh kelas yang mendeklarasikannya, sedangkan kelas lain hanya boleh mendaftar dan melepas penangan melalui += dan -=. Mekanisme ini menjadi dasar pola publisher-subscriber, yaitu pola ketika satu penerbit (publisher) mengumumkan kejadian dan banyak pelanggan (subscriber) meresponsnya tanpa saling mengenal. Topik ini membahas celah keamanan delegate publik yang dihadapi event, cara mendeklarasikan dan memancarkan event, serta peran masing-masing pihak pada pola tersebut. Setelah topik ini, kelompok kelas dapat saling terhubung melalui kejadian tanpa referensi langsung antar-kelasnya.
Masalah pada delegate publik#
Delegate yang diekspos sebagai anggota publik tanpa kata kunci event memberi kebebasan penuh kepada kelas lain, termasuk kebebasan yang merugikan. Kelas luar dapat memanggil delegate tersebut kapan saja seolah-olah menjadi penerbitnya, dan lebih berbahaya lagi, kelas luar dapat menimpanya dengan = sehingga seluruh penangan yang sudah terdaftar hilang dalam satu penugasan. Penerbit kehilangan kendali atas kejadiannya sendiri, sedangkan pelanggan kehilangan penangan yang telah didaftarkan.
Kata kunci event menambahkan lapisan pembatas di atas anggota delegate biasa. Perbedaannya terlihat pada tabel berikut.
| Perilaku dari luar kelas penerbit | Delegate publik | Event |
|---|---|---|
| Menambah atau melepas penangan | Boleh, melalui += dan -= | Boleh, hanya melalui += dan -= |
| Memanggil delegate secara langsung | Boleh | Ditolak kompilator |
Menimpa seluruh penangan dengan = | Boleh | Ditolak kompilator |
Pembatasan tersebut tidak mengubah penyimpanannya, karena di dalam kelas penerbit event tetap merupakan medan bertipe delegate yang dapat dipanggil seperti biasa. Kendali penuh atas pemanggilan tetap berada pada kelas yang mendeklarasikan event, sehingga kejadian hanya dipancarkan oleh penerbit yang sah.
Mendeklarasikan dan memancarkan event#
Pembuatan event terdiri atas tiga langkah, yaitu mendeklarasikan tipe delegate yang menjadi tanda tangan event, mendeklarasikan anggota event dengan kata kunci event, dan memancarkannya dari dalam kelas penerbit. Pemancaran dilakukan dengan memanggil event seperti memanggil delegate, dan karena event bisa saja belum memiliki pelanggan, pemanggilannya dilindungi operator ?. agar tidak melempar NullReferenceException ketika belum ada yang berlangganan.
Program utuh berikut memperlihatkan ketiga langkah tersebut pada kelas SensorSuhu yang memancarkan event SuhuBerubah. Salin programnya ke berkas Program.cs, lalu jalankan dengan dotnet run --file Program.cs.
// Program dasar event: kata kunci event, penerbit, dan pelanggan.
SensorSuhu sensor = new();
// Dua pelanggan mendaftarkan metode penanganan masing-masing.
sensor.SuhuBerubah += Tampilkan;
sensor.SuhuBerubah += Catat;
sensor.Ukur(30);
sensor.Ukur(33);
// Metode penanganan pelanggan yang menampilkan suhu terbaru.
static void Tampilkan(object pengirim, double suhu)
{
Console.WriteLine($"Tampilan: suhu terbaru {suhu} derajat.");
}
// Metode penanganan pelanggan kedua yang menyimpan catatan.
static void Catat(object pengirim, double suhu)
{
Console.WriteLine($"Catatan: suhu {suhu} direkam ke berkas log.");
}
// Penerbit yang memiliki dan memancarkan event SuhuBerubah.
class SensorSuhu
{
// Tipe delegate yang menjadi bentuk tanda tangan event.
public delegate void SuhuBerubahHandler(object pengirim, double suhu);
// Kata kunci event membuat delegate hanya dapat dipicu dari kelas ini.
public event SuhuBerubahHandler? SuhuBerubah;
// Metode penerbit yang membaca sensor lalu memancarkan event.
public void Ukur(double suhu)
{
SuhuBerubah?.Invoke(this, suhu);
}
}Tampilan: suhu terbaru 30 derajat.
Catatan: suhu 30 direkam ke berkas log.
Tampilan: suhu terbaru 33 derajat.
Catatan: suhu 33 direkam ke berkas log.Setiap pemanggilan Ukur memancarkan event satu kali, dan kedua pelanggan merespons sesuai urutan pendaftarannya. Kelas SensorSuhu tidak mengenal metode Tampilkan maupun Catat pada saat kelasnya ditulis, karena keduanya baru terhubung melalui pendaftaran di badan program. Hubungan terbalik inilah yang membuat penerbit tetap berguna untuk pelanggan mana pun yang datang kemudian.
Baris SuhuBerubah?.Invoke(this, suhu) mengirimkan dua hal, yaitu objek penerbit melalui this dan data kejadian sebagai argumen kedua. Apabila baris pemanggilan yang sama ditulis di badan program, yang berarti di luar kelas SensorSuhu, kompilator menolaknya seperti pada contoh berikut.
// tidak-dijalankan
// Baris berikut ditolak dengan error CS0070, karena event hanya dapat
// dipicu dari dalam kelas yang mendeklarasikannya.
sensor.SuhuBerubah(sensor, 30);Pola publisher-subscriber#
Pola publisher-subscriber menetapkan dua peran yang terpisah sepenuhnya. Penerbit adalah kelas yang memiliki status atau pekerjaan tertentu, mendeklarasikan event, dan memancarkannya ketika kejadian terjadi, misalnya nilai sensor berubah atau unduhan selesai. Pelanggan adalah pihak yang membutuhkan kabar tersebut, menuliskan metode penanganan dengan tanda tangan yang cocok, lalu mendaftarkannya ke event penerbit. Penerbit tidak mengetahui daftar pelanggannya, dan pelanggan tidak mengganggu alur kerja penerbit.
Pola ini lazim dijumpai di .NET, misalnya tombol antarmuka yang memancarkan event Click, atau aliran data yang memancarkan event ketika pembacaan selesai. Dengan pola tersebut, penerbit dapat ditulis lebih dahulu tanpa mengetahui semua respons yang akan mengikutinya, dan pelanggan baru dapat ditambahkan dengan satu baris += tanpa mengubah kode penerbit.
Kelebihan pola ini paling terasa ketika respons baru diminta setelah penerbit selesai ditulis. Kelas penerbit yang sudah jadi dapat melayani pelanggan yang belum pernah dibayangkan pembuatnya, karena pendaftaran cukup dilakukan dari sisi pelanggan tanpa mengubah satu baris pun di kelas penerbit. Pemisahan ini juga menyederhanakan pengujian, yaitu penerbit dapat diuji dengan mendaftarkan penangan tiruan yang mencatat setiap pemanggilan event.
Kesalahan umum#
Tiga kesalahan berikut paling sering muncul ketika event pertama kali digunakan.
- Event dipanggil dari luar kelas penerbitnya. Kompilator menolaknya dengan error CS0070 karena kata kunci
eventmemang melarang pemanggilan dari luar, sehingga pemancaran perlu dipindahkan ke metode milik kelas penerbit. - Event dipancarkan tanpa pemeriksaan
null. Event tanpa pelanggan bernilainull, sehingga pemanggilanSuhuBerubah(this, suhu)tanpa?.melemparNullReferenceExceptionpada pelanggan pertama yang belum terdaftar. - Penangan diharapkan mengembalikan hasil ke penerbit. Tipe delegate untuk event lazimnya
void, dan pelanggan dengan tanda tangan yang berbeda tidak dapat didaftarkan, sehingga data hanya mengalir dari penerbit ke pelanggan melalui parameter.
Ringkasan#
- Event adalah delegate yang dibungkus kata kunci
eventsehingga hanya kelas penerbit yang boleh memicunya, sedangkan kelas luar hanya boleh+=dan-=. - Pemancaran event dilakukan dari dalam kelas penerbit dan dilindungi
?.Invoke()karena event tanpa pelanggan bernilainull. - Pola publisher-subscriber memisahkan penerbit dari pelanggan, sehingga respons baru dapat didaftarkan tanpa mengubah kode penerbit.
Referensi#
- The
eventkeyword — Microsoft Learn — Referensi kata kuncieventbeserta pembatasan akses yang diberikannya. - Introduction to delegates and events — Microsoft Learn — Perbandingan delegate biasa dengan event dan alasan pembungkusannya.
- Handling and raising events — Microsoft Learn — Panduan event di .NET, termasuk cara mengirim dan menangani kejadian.