Menu

Delegate C#: Deklarasi, Invoke, Multicast, dan Callback

Apa itu delegate di C# dan cara memakainya: mendeklarasikan tipe delegate, membuatnya dari method, lambda, atau anonymous method, memanggilnya, delegate sebagai callback, multicast delegate dengan += dan -=, pengecekan null dengan ?.Invoke, dan hubungan delegate dengan Func, Action, dan event.

Halaman ini dilengkapi editor yang bisa dijalankan. Edit, jalankan, dan lihat output-nya seketika.

Delegate adalah tipe yang mewakili sebuah signature method. Variabel bertipe delegate menyimpan reference ke method dengan signature itu, dan memanggil variabelnya berarti memanggil method tersebut. Begitulah C# memperlakukan fungsi sebagai nilai: kamu bisa menyimpannya, mengopernya ke method lain, dan memanggilnya nanti.

Output:

40
70
68.00

delegate decimal PriceRule(decimal price); mendeklarasikan tipe baru, sama seperti class. Method apa pun yang menerima satu decimal dan mengembalikan decimal cocok dengannya. Variabel rule bergantian menunjuk ke tiga method berbeda, dan setiap panggilan menjalankan method mana pun yang sedang disimpannya.

Mendeklarasikan, membuat, dan memanggil

Deklarasi tipe delegate terlihat seperti signature method dengan keyword delegate di depannya. Biasanya deklarasi ini berada di tingkat namespace, berdampingan dengan class, atau bersarang di dalam sebuah class.

Ada empat cara untuk membuat instance delegate:

PriceRule a = HalfPrice;                             // method group (most common)
PriceRule b = new PriceRule(HalfPrice);              // explicit constructor, same thing
PriceRule c = p => p - 5m;                            // lambda expression
PriceRule d = delegate (decimal p) { return p; };    // anonymous method (C# 2 syntax)

Dan ada dua cara untuk memanggilnya, yang hasilnya identik: rule(80m) dan rule.Invoke(80m). Compiler memeriksa signature-nya saat assignment, jadi method yang tidak cocok menghasilkan error compile, bukan kejutan saat runtime.

Delegate juga bisa menunjuk ke method instance. Dalam hal itu, delegate mengingat method-nya sekaligus object tempat method itu dipanggil:

Output:

Ana got: Your order shipped
target: Mailbox, method: Receive

Target adalah object tempat delegate akan memanggil method-nya (null untuk method static), dan Method menjelaskan method-nya. Menyimpan target juga berarti delegate membuat object itu tetap hidup, dan hal ini penting untuk event.

Delegate sebagai callback

Tugas utama delegate adalah membuat sebuah method bisa memanggil kode yang dipilih oleh pemanggilnya. Method itu menentukan kapan sesuatu terjadi; pemanggil menentukan apa yang terjadi:

Output:

[##        ] 25%
[#####     ] 50%
[#######   ] 75%
[##########] 100%
import finished

ImportRows tidak tahu apa-apa tentang console atau progress bar. Method yang sama bisa menggerakkan progress bar, baris log, atau pembaruan UI, tergantung apa yang dikirim pemanggilnya. List.Sort(Comparison<T>), Array.Find(Predicate<T>), Task.Run(Action), dan setiap operator LINQ mengikuti pola ini.

Multicast delegate: += dan -=

Sebuah delegate bisa menyimpan lebih dari satu method. += menambahkan method ke invocation list-nya, -= menghapusnya, dan memanggil delegate itu akan memanggil setiap method secara berurutan:

Output:

email sent for A-1001
stock updated for A-1001
warehouse notified for A-1001
3 handlers
email sent for A-1002
warehouse notified for A-1002

Ada tiga detail multicast delegate yang penting dalam praktik:

  • Nilai kembalian: kalau tipe delegate-nya mengembalikan nilai, memanggil multicast delegate hanya mengembalikan hasil dari method terakhir. Untuk mengumpulkan semua hasilnya, lakukan perulangan pada GetInvocationList() dan panggil setiap method sendiri.
  • Exception: kalau satu method melempar exception, method sisanya tidak dijalankan dan exception-nya sampai ke pemanggil.
  • Immutability: delegate bersifat immutable. += membuat delegate baru dengan list yang lebih panjang lalu meng-assign-nya kembali ke variabel, itulah sebabnya menghapus dengan -= pada salinannya tidak memengaruhi yang asli.

Menghapus lambda dengan -= hanya berhasil kalau kamu memberikan instance delegate yang sama. Menulis lambda yang sama dua kali menghasilkan dua delegate yang berbeda, jadi handlers -= id => Log(id); tidak menghapus apa pun. Simpan lambda-nya di variabel kalau kamu berencana berhenti berlangganan (unsubscribe).

Delegate null dan ?.Invoke

Variabel delegate yang belum diisi apa pun bernilai null, dan menghapus method terakhir dengan -= juga menyisakan null. Memanggil null melempar NullReferenceException:

Output:

no handler, no crash
saved draft.txt
NullReferenceException

?.Invoke adalah cara standar untuk memanggil callback yang opsional. Operator ?. tidak bisa ditaruh langsung sebelum tanda kurung (onSaved?("x") bukan sintaks yang valid), jadi pola ini memanggil method Invoke milik delegate dengan namanya, yang fungsinya sama dengan panggilan singkat.

Delegate, Func, Action, dan event

Mendeklarasikan tipe delegate untuk setiap signature akan merepotkan, jadi .NET menyediakan tipe yang generic:

Tipe bawaanDeklarasi buatan sendiri yang setara
Actiondelegate void Action();
Action<T>delegate void Action<T>(T arg);
Func<TResult>delegate TResult Func<TResult>();
Func<T, TResult>delegate TResult Func<T, TResult>(T arg);
Predicate<T>delegate bool Predicate<T>(T obj);

PriceRule dari contoh pertama punya bentuk yang sama dengan Func<decimal, decimal>, tetapi keduanya tipe yang berbeda dan tidak bisa dikonversi satu sama lain. Bahkan dua tipe bawaan dengan signature identik pun tidak bisa:

Func<int, bool> isPositive = x => x > 0;
Predicate<int> p = isPositive;
// error CS0029: Cannot implicitly convert type 'System.Func<int, bool>' to 'System.Predicate<int>'

Predicate<int> ok = new Predicate<int>(isPositive);   // wrap it explicitly
Predicate<int> ok2 = x => isPositive(x);              // or with a lambda

Lambda bisa dikonversi ke tipe delegate apa pun yang kompatibel, jadi API biasanya menerima lambda dan pertanyaan ini tidak pernah muncul. Halaman Func dan Action membahas keluarga tipe generic ini dan kapan deklarasi buatan sendiri masih layak dipakai.

Event dibangun langsung di atas delegate. Field event adalah multicast delegate yang hanya boleh di-+= dan di--= oleh kode dari luar, tidak pernah dipanggil atau ditimpa. Daftar subscriber di contoh multicast di atas persis yang dikelola event untukmu, dengan perlindungan tambahan itu. Event dibahas di halamannya sendiri.

Pertanyaan yang sering diajukan

Apa itu delegate di C#?

Delegate adalah reference ke sebuah method yang type-safe. Kamu mendeklarasikan tipe delegate dengan sebuah signature, misalnya delegate decimal PriceRule(decimal price);, dan method apa pun dengan signature itu (static, instance, atau lambda) bisa disimpan di variabel bertipe tersebut lalu dipanggil nanti lewat variabel itu. Delegate adalah cara C# mengoper fungsi sebagai nilai.

Apa bedanya delegate dan Func di C#?

Func<T, TResult> dan Action<T> adalah tipe delegate yang sudah dideklarasikan .NET untukmu, generic atas tipe parameter dan tipe kembaliannya. Deklarasi delegate buatan sendiri memberi tipe itu nama dan nama parameter yang menjelaskan perannya, serta mendukung parameter ref, out, dan in. Untuk sebagian besar kode, Func dan Action sudah cukup.

Apa itu multicast delegate di C#?

Delegate yang menyimpan beberapa method. += menambahkan method ke invocation list-nya dan -= menghapusnya; memanggil delegate itu akan memanggil semuanya secara berurutan. Kalau delegate-nya mengembalikan nilai, pemanggil hanya menerima hasil dari method terakhir, dan exception di satu method menghentikan method sisanya.

Bagaimana cara memanggil delegate yang mungkin null dengan aman?

Pakai operator null-conditional: onProgress?.Invoke(50);. Variabel delegate yang belum diisi method apa pun bernilai null, dan memanggilnya secara langsung melempar NullReferenceException. ?.Invoke membaca variabelnya sekali, jadi tetap aman meskipun thread lain menghapus handler terakhir di saat yang sama.

Kapan sebaiknya mendeklarasikan tipe delegate sendiri alih-alih memakai Func atau Action?

Saat signature-nya butuh parameter ref, out, atau in (Func dan Action tidak bisa menyatakannya), saat tipe yang bernama membuat API publik lebih jelas (delegate bool Validator(string input)), atau saat kamu ingin nama parameternya muncul di IntelliSense. Selain itu, utamakan Func dan Action, yang dikenali setiap developer .NET.

Ilustrasi bahasa pemrograman di Coddy

Belajar coding dengan Coddy

Mulai