Bagikan melalui


System.Delegate dan kata kunci delegate

Sebelumnya

Artikel ini membahas kelas di .NET yang mendukung delegasi, dan bagaimana kelas-kelas tersebut memetakan ke kata kunci delegate.

Apa itu delegasi?

Anggap delegate sebagai cara untuk menyimpan referensi ke sebuah metode, mirip dengan bagaimana Anda dapat menyimpan referensi ke objek. Sama seperti Anda dapat meneruskan objek ke metode, Anda dapat meneruskan referensi metode menggunakan delegasi. Ini berguna ketika Anda ingin menulis kode fleksibel di mana metode yang berbeda dapat "dicolokkan" untuk memberikan perilaku yang berbeda.

Misalnya, bayangkan Anda memiliki kalkulator yang dapat melakukan operasi pada dua angka. Alih-alih penambahan, pengurangan, perkalian, dan pembagian hard-coding ke dalam metode terpisah, Anda dapat menggunakan delegasi untuk mewakili operasi apa pun yang mengambil dua angka dan mengembalikan hasilnya.

Menentukan jenis delegasi

Sekarang mari kita lihat cara membuat tipe delegate menggunakan kata kunci delegate. Saat Anda menentukan jenis delegasi, Anda pada dasarnya membuat templat yang menjelaskan jenis metode apa yang dapat disimpan dalam delegasi tersebut.

Anda menentukan jenis delegasi menggunakan sintaks yang terlihat mirip dengan tanda tangan metode, tetapi dengan delegate kata kunci di awal:

// Define a simple delegate that can point to methods taking two integers and returning an integer
public delegate int Calculator(int x, int y);

Delegasi ini Calculator dapat menampung referensi ke metode apa pun yang menerima dua parameter int dan mengembalikan int.

Mari kita lihat contoh yang lebih praktis. Saat Anda ingin mengurutkan daftar, Anda perlu memberi tahu algoritma pengurutan cara membandingkan item. Mari kita lihat bagaimana delegasi membantu dengan metode List.Sort(). Langkah pertama adalah membuat jenis delegasi untuk operasi perbandingan:

// From the .NET Core library
public delegate int Comparison<in T>(T left, T right);

Delegasi ini Comparison<T> dapat menyimpan referensi ke metode apa pun yang:

  • Mengambil dua parameter bertipe T
  • Mengembalikan int (biasanya -1, 0, atau 1 untuk menunjukkan "kurang dari", "sama dengan", atau "lebih besar dari")

Saat Anda menentukan jenis delegasi seperti ini, pengkompilasi secara otomatis menghasilkan kelas yang berasal dari System.Delegate yang cocok dengan tanda tangan Anda. Kelas ini menangani semua kompleksitas penyimpanan dan pemanggilan referensi metode untuk Anda.

Jenis delegasi Comparison adalah jenis generik, yang berarti dapat bekerja dengan jenis apa pun T. Untuk informasi selengkapnya tentang generik, lihat Kelas dan metode generik.

Perhatikan bahwa meskipun sintaks terlihat mirip dengan mendeklarasikan variabel, Anda benar-benar mendeklarasikan jenis baru. Anda dapat menentukan jenis delegasi di dalam kelas, langsung di dalam namespace, atau bahkan di namespace global.

Nota

Mendeklarasikan tipe delegasi (atau tipe lainnya) secara langsung di ruang nama global tidak disarankan.

Pengkompilasi juga menghasilkan penambahan dan penghapusan handler untuk jenis baru ini sehingga klien kelas ini dapat menambahkan dan menghapus metode dari daftar pemanggilan instans. Kompilator memberlakukan bahwa signature dari metode yang ditambahkan atau dihapus harus cocok dengan signature yang digunakan saat mendeklarasikan tipe delegate.

Mendeklarasikan instans delegasi

Setelah menentukan jenis delegasi, Anda dapat membuat instans (variabel) dari jenis tersebut. Anggap ini sebagai membuat "slot" di mana Anda dapat menyimpan referensi ke metode .

Seperti semua variabel dalam C#, Anda tidak dapat mendeklarasikan instans delegasi langsung di dalam namespace atau di namespace global.

// Inside a class definition:
public Comparison<T> comparator;

Jenis variabel ini adalah Comparison<T> (jenis delegasi yang Anda tentukan sebelumnya), dan nama variabelnya adalah comparator. Pada titik ini, comparator belum menunjuk ke metode apa pun—seperti slot kosong yang menunggu untuk diisi.

Anda juga dapat mendeklarasikan variabel delegasi sebagai variabel lokal atau parameter metode, sama seperti jenis variabel lainnya.

Memanggil delegasi

Setelah Anda memiliki instans delegat yang menunjuk ke metode, Anda dapat memanggil metode tersebut melalui delegat. Anda memanggil metode yang ada dalam daftar pemanggilan delegasi dengan memanggil delegasi tersebut seolah-olah itu adalah metode.

Berikut adalah cara Sort() metode menggunakan delegasi perbandingan untuk menentukan urutan objek:

int result = comparator(left, right);

Dalam baris ini, kode memanggil metode yang dilampirkan ke delegat. Anda memperlakukan variabel delegasi seolah-olah itu adalah nama metode dan menyebutnya menggunakan sintaks panggilan metode normal.

Namun, baris kode ini membuat asumsi yang tidak aman: beranggapan bahwa metode target telah ditambahkan ke delegate. Jika tidak ada metode yang terpasang, garis di atas akan menyebabkan NullReferenceException dilempar. Pola yang digunakan untuk mengatasi masalah ini lebih canggih daripada pemeriksaan null sederhana, dan dibahas nanti dalam seri ini.

Menetapkan, menambahkan, dan menghapus target pemanggilan

Sekarang Anda tahu cara menentukan jenis delegasi, mendeklarasikan instans delegasi, dan memanggil delegasi. Tetapi bagaimana Anda benar-benar menghubungkan metode ke delegasi? Di sinilah penugasan delegasi masuk.

Untuk menggunakan delegasi, Anda perlu menetapkan metode untuk itu. Metode yang Anda tetapkan harus memiliki tanda tangan yang sama (parameter dan jenis pengembalian yang sama) seperti yang ditentukan jenis delegasi.

Mari kita lihat contoh praktis. Misalkan Anda ingin mengurutkan daftar string menurut panjangnya. Anda perlu membuat metode perbandingan yang cocok dengan Comparison<string> tanda tangan delegasi:

private static int CompareLength(string left, string right) =>
    left.Length.CompareTo(right.Length);

Metode ini mengambil dua string dan mengembalikan bilangan bulat yang menunjukkan string mana yang "lebih besar" (lebih lama dalam kasus ini). Metode ini dinyatakan sebagai privat, hal ini sangatlah tepat. Anda tidak memerlukan metode untuk menjadi bagian dari antarmuka publik Anda untuk menggunakannya dengan delegasi.

Sekarang Anda dapat meneruskan metode ini ke List.Sort() metode :

phrases.Sort(CompareLength);

Perhatikan bahwa Anda menggunakan nama metode tanpa tanda kurung. Ini memberi tahu pengkompilasi untuk mengonversi referensi metode menjadi delegasi yang dapat dipanggil nanti. Metode ini Sort() akan memanggil metode Anda CompareLength setiap kali perlu membandingkan dua string.

Anda juga dapat lebih eksplisit dengan mendeklarasikan variabel delegasi dan menetapkan metode untuk itu:

Comparison<string> comparer = CompareLength;
phrases.Sort(comparer);

Kedua pendekatan mencapai hal yang sama. Pendekatan pertama lebih ringkas, sementara yang kedua membuat penugasan delegasi lebih eksplisit.

Untuk metode sederhana, umumnya menggunakan ekspresi lambda alih-alih menentukan metode terpisah:

Comparison<string> comparer = (left, right) => left.Length.CompareTo(right.Length);
phrases.Sort(comparer);

Ekspresi Lambda menyediakan cara yang ringkas untuk menentukan metode sederhana sebaris. Menggunakan ekspresi lambda untuk mendelegasikan target dibahas secara lebih rinci di bagian selanjutnya.

Contoh sejauh ini menunjukkan delegasi dengan satu metode sasaran. Namun, objek delegasi dapat mendukung daftar pemanggilan yang memiliki beberapa metode target yang dilampirkan ke satu objek delegasi. Kemampuan ini sangat berguna untuk skenario penanganan peristiwa.

Kelas Delegate dan MulticastDelegate

Di balik layar, fitur delegasi yang telah Anda gunakan dibangun pada dua kelas utama dalam kerangka kerja .NET: Delegate dan MulticastDelegate. Anda biasanya tidak bekerja dengan kelas-kelas ini secara langsung, tetapi mereka menyediakan fondasi yang membuat delegasi berfungsi.

Kelas System.Delegate dan subkelas System.MulticastDelegate langsungnya menyediakan dukungan kerangka kerja untuk membuat delegasi, mendaftarkan metode sebagai target delegasi, dan memanggil semua metode yang terdaftar di delegasi.

Berikut adalah rincian desain yang menarik: System.Delegate dan System.MulticastDelegate bukan tipe delegasi itu sendiri yang dapat Anda gunakan. Sebaliknya, mereka berfungsi sebagai kelas dasar untuk semua jenis delegasi tertentu yang Anda buat. Bahasa C# mencegah Anda langsung mewarisi dari kelas-kelas ini—Anda harus menggunakan kata kunci sebagai gantinya delegate .

Saat Anda menggunakan delegate kata kunci untuk mendeklarasikan jenis delegasi, pengkompilasi C# secara otomatis membuat kelas yang berasal dari MulticastDelegate dengan tanda tangan spesifik Anda.

Mengapa desain ini?

Desain ini memiliki akar dalam rilis pertama C# dan .NET. Tim desain memiliki beberapa tujuan:

  1. Kekokohan Jenis: Tim ingin memastikan bahwa bahasa menegakkan kekokohan jenis saat menggunakan delegasi. Ini berarti memastikan bahwa delegasi dipanggil dengan jenis dan jumlah argumen yang benar, dan bahwa jenis pengembalian diverifikasi dengan benar pada waktu kompilasi.

  2. Performa: Dengan meminta kompilator menghasilkan kelas delegasi konkret yang mewakili tanda tangan metode tertentu, runtime dapat mengoptimalkan pemanggilan delegasi.

  3. Kesederhanaan: Delegasi disertakan dalam rilis .NET 1.0, yang sebelum generik diperkenalkan. Desain harus bekerja sesuai dengan keterbatasan zaman.

Solusinya adalah agar kompilator membuat kelas delegasi konkret yang sesuai dengan tanda tangan metode Anda, memastikan keamanan tipe sambil menyembunyikan kompleksitas dari Anda.

Bekerja dengan metode delegasi

Meskipun Anda tidak dapat membuat kelas turunan secara langsung, Anda kadang-kadang akan menggunakan metode yang ditentukan pada kelas Delegate dan MulticastDelegate. Berikut adalah aspek-aspek yang paling penting yang perlu Anda ketahui:

Setiap delegasi yang Anda kerjakan berasal dari MulticastDelegate. Delegasi "multicast" berarti bahwa lebih dari satu target metode dapat dipanggil saat memanggil melalui delegasi. Desain asli yang dianggap membuat perbedaan antara delegasi yang hanya dapat memanggil satu metode versus delegasi yang dapat memanggil beberapa metode. Dalam praktiknya, perbedaan ini terbukti kurang berguna daripada yang diduga awalnya, sehingga semua delegasi dalam .NET mendukung beberapa metode target.

Metode yang paling umum digunakan saat bekerja dengan delegasi adalah:

  • Invoke(): Memanggil semua metode yang dilampirkan ke delegate
  • BeginInvoke() / EndInvoke(): Digunakan untuk pola pemanggilan asinkron (meskipun async/await sekarang lebih disukai)

Dalam kebanyakan kasus, Anda tidak akan memanggil metode ini secara langsung. Sebagai gantinya, Anda akan menggunakan sintaks panggilan metode pada variabel delegasi, seperti yang ditunjukkan pada contoh di atas. Namun, seperti yang akan Anda lihat nanti dalam seri ini, ada pola yang berfungsi langsung dengan metode ini.

Ringkasan

Sekarang setelah Anda melihat bagaimana sintaks bahasa C# berkaitan dengan kelas .NET yang mendasarinya, Anda siap untuk menjelajahi bagaimana delegasi yang bertipe kuat digunakan, dibuat, dan dipanggil pada skenario yang lebih kompleks.

Berikutnya