Func dan Action adalah tipe delegate generik yang sudah dideklarasikan .NET untukmu, jadi kamu bisa menyimpan dan mengirim fungsi tanpa harus mendeklarasikan tipe delegate terlebih dahulu. Func mengembalikan nilai; Action tidak mengembalikan apa pun.
Output:
4
46.00
False
Hello!
Mia scored 87
Aturan yang perlu diingat: di sebuah Func, argumen tipe terakhir adalah tipe kembalian. Func<int, int, bool> menerima dua int dan mengembalikan sebuah bool. Func<int> tidak menerima apa pun dan mengembalikan sebuah int. Semua argumen tipe sebuah Action adalah parameter.
Keluarganya
| Tipe | Parameter | Mengembalikan |
|---|---|---|
Action | tidak ada | void |
Action<T> | T | void |
Action<T1, T2> ... sampai 16 | T1, T2, ... | void |
Func<TResult> | tidak ada | TResult |
Func<T, TResult> | T | TResult |
Func<T1, T2, TResult> ... sampai 16 | T1, T2, ... | TResult |
Predicate<T> | T | bool |
Comparison<T> | T, T | int (negatif, nol, positif) |
Converter<TIn, TOut> | TIn | TOut |
Predicate, Comparison dan Converter lebih tua dan muncul di method milik List<T> dan Array: FindAll(Predicate<T>), Sort(Comparison<T>), ConvertAll(Converter<T, TOut>). LINQ memakai Func di semua tempat, itulah sebabnya list.Where(...) dan list.FindAll(...) menerima lambda dengan bentuk yang sama tapi tipe yang berbeda:
Output:
17, 15
34, 52, 28
52, 34, 28, 17, 15
52y 34y 28y 17y 15y
Mengirim fungsi ke method
Parameter Func atau Action membuat pemanggil bisa menyediakan bagian algoritma yang berubah-ubah. Dua bentuk yang umum adalah "jalankan ini dan ukur waktunya" serta "coba ini sampai berhasil":
Output:
attempt 1 failed
attempt 2 failed
<html>ok</html>
clicks: 2
Retry adalah method generik, jadi Func<int, T> miliknya bisa mengembalikan apa saja; compiler menyimpulkan T sebagai string dari lambda-nya. Lambda yang dikirim ke Twice mengubah variabel lokal clicks, dan ini berhasil karena lambda menangkap variabelnya sendiri, bukan salinan nilainya.
Method group: mengirim method lewat namanya
Kamu tidak butuh lambda untuk mengisi Func atau Action. Nama method yang sudah ada dengan signature yang cocok (sebuah "method group") bisa langsung dikonversi:
Output:
parsed: 43
sat is weekend: True
total quantity: 20
Select(int.Parse) sama dengan Select(s => int.Parse(s)), hanya saja tanpa satu lapisan pemanggilan. Compiler memilih overload yang sesuai dengan tipe tujuannya: int.Parse punya beberapa overload, dan hanya Parse(string) yang cocok dengan Func<string, int>. Kalau compiler tidak bisa menentukan satu pilihan (beberapa overload sama-sama cocok, atau argumen tipe sebuah method generik tidak bisa disimpulkan dari method group), pemanggilannya tidak bisa di-compile, dan lambda yang menuliskan pemanggilannya secara eksplisit akan memperbaikinya.
Method group yang dipakai sebagai Func tetap dijalankan nanti, setiap kali delegate itu dipanggil, sama persis seperti lambda. Bedanya hanya pada cara penulisannya.
Mengembalikan fungsi
Sebuah method bisa membuat dan mengembalikan fungsi. Func yang dikembalikan membawa nilai-nilai yang dipakai untuk membuatnya:
Output:
140
180
162
Twice(staff) membuat fungsi baru yang menerapkan diskon karyawan dua kali: 200, lalu 180, lalu 162.
Dictionary berisi fungsi
Menyimpan fungsi di dalam dictionary mengubah switch yang panjang menjadi tabel pencarian. Ini bentuk yang biasa dipakai untuk penangan perintah, kalkulator dan aksi menu, dan entri baru bisa ditambahkan saat program berjalan:
Output:
12 + 30 = 42
7 * 6 = 42
9 / 0 = cannot divide by zero
2 ^ 3 = unknown operator
Hello, Sam
DONE
Menambah operator cukup dengan satu entri dictionary lagi, tanpa mengubah loop-nya. StringComparer.OrdinalIgnoreCase yang dikirim ke dictionary kedua membuat nama perintah tidak membedakan huruf besar dan kecil.
Fungsi async: Func<Task>, bukan Action
Pada kode async, tipe delegate menentukan apakah pemanggil bisa menunggu pekerjaannya selesai:
Func<Task> save = async () => await File.WriteAllTextAsync("a.txt", "data");
await save(); // the caller can await it and see its exceptions
Func<int, Task<string>> load = async id => await FetchUserAsync(id);
string user = await load(42);
Action bad = async () => await File.WriteAllTextAsync("a.txt", "data");
bad(); // async void: fire and forget, exceptions escape
Lambda async yang diberikan ke Action menjadi method async void: tidak ada yang bisa melakukan await padanya, dan exception di dalamnya tidak pernah sampai ke pemanggil. Exception itu dilempar ulang di thread pool (atau di thread UI pada aplikasi desktop), dan biasanya membuat proses crash. Saat kamu menulis method yang menerima pekerjaan async, terimalah Func<Task> (atau Func<Task<T>>).
Kapan mendeklarasikan delegate sendiri
Func dan Action mencukupi sebagian besar kebutuhan. Deklarasikan tipe delegate bernama saat:
- Signature-nya punya parameter
ref,outatauin.Functidak bisa menyatakannya, jadi fungsi bergayaTryParsebutuhdelegate bool TryParser<T>(string text, out T value);. - Namanya menjelaskan sebuah peran di API publik.
delegate bool Validator(string input)di signature sebuah method memberi informasi lebih banyak daripadaFunc<string, bool>, dan nama parameternya muncul di IntelliSense. - Kamu butuh parameter
params, yang juga tidak bisa dimiliki delegate generik.
Event adalah tempat lain di mana tipe delegate kustom masih muncul, walaupun EventHandler<TEventArgs> bawaan sudah mencakup sebagian besarnya. Halaman delegate membahas cara mendeklarasikan dan menggabungkan tipe delegate.
Pertanyaan yang sering diajukan
Apa perbedaan Func dan Action di C#?
Func mengembalikan nilai dan Action tidak. Di Func<int, string, bool>, argumen tipe terakhir (bool) adalah tipe kembalian dan sisanya adalah parameter. Action<int, string> menerima sebuah int dan sebuah string lalu mengembalikan void. Keduanya tersedia dalam versi dengan 0 sampai 16 parameter.
Bagaimana cara mengirim fungsi sebagai parameter di C#?
Deklarasikan parameternya sebagai Func atau Action dengan bentuk yang sesuai, lalu panggil di dalam method: static decimal Apply(decimal price, Func<decimal, decimal> rule) => rule(price);. Pemanggil mengirim lambda (Apply(80m, p => p * 0.9m)) atau nama method (Apply(80m, HalfPrice)).
Apa itu Predicate<T> di C#?
Tipe delegate yang menerima T dan mengembalikan bool, bentuknya sama dengan Func<T, bool>. Tipe ini lebih tua dari Func dan dipakai oleh List<T>.Find, FindAll, RemoveAll, Exists dan Array.Find. Kedua tipe itu tidak bisa dikonversi satu sama lain, tapi sebuah lambda bisa dikonversi ke salah satunya.
Bagaimana cara memakai Func dengan kode async?
Pakai Func<Task> untuk fungsi async tanpa hasil dan Func<T, Task<TResult>> untuk fungsi async dengan hasil, supaya pemanggil bisa melakukan await. Hindari memberikan lambda async ke Action: itu membuatnya menjadi async void, yang tidak bisa di-await dan exception-nya tidak bisa ditangkap oleh pemanggil.
Apakah Func bisa punya parameter out atau ref?
Tidak. Parameter generik Func dan Action adalah parameter nilai biasa, jadi tidak ada cara untuk menulis Func<string, out int, bool>. Untuk signature seperti TryParse, deklarasikan delegate kustom: delegate bool TryParser<T>(string text, out T value);.