Event

Kata kunci event dan pola publisher-subscriber.

Daftar isi

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 penerbitDelegate publikEvent
Menambah atau melepas penanganBoleh, melalui += dan -=Boleh, hanya melalui += dan -=
Memanggil delegate secara langsungBolehDitolak kompilator
Menimpa seluruh penangan dengan =BolehDitolak 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.

csharp 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);
    }
}
text Keluaran
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.

csharp Contoh: pemanggilan event dari luar kelas ditolak kompilator
// 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 event memang melarang pemanggilan dari luar, sehingga pemancaran perlu dipindahkan ke metode milik kelas penerbit.
  • Event dipancarkan tanpa pemeriksaan null. Event tanpa pelanggan bernilai null, sehingga pemanggilan SuhuBerubah(this, suhu) tanpa ?. melempar NullReferenceException pada 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 event sehingga 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 bernilai null.
  • Pola publisher-subscriber memisahkan penerbit dari pelanggan, sehingga respons baru dapat didaftarkan tanpa mengubah kode penerbit.

Referensi#