Lambda expression adalah fungsi anonim kecil yang ditulis langsung di tempat dengan operator => (dibaca "goes to"). Parameternya ditulis di sebelah kiri, hasilnya di sebelah kanan:
Output:
60.0
12
ORDER SHIPPED!
96.0, 180.0
Lambda tidak punya tipe sendiri. Ia menjadi sebuah delegate (objek yang menunjuk ke sebuah fungsi) dengan tipe delegate apa pun yang diharapkan konteksnya: di sini Func<decimal, decimal>, Predicate<T> untuk List.FindAll, atau EventHandler untuk sebuah event. Delegate Func mengembalikan nilai dan delegate Action tidak; halaman Func dan Action berisi daftar lengkap keluarganya.
Bentuk-bentuk sintaks
x => x * x // one parameter: parentheses optional
(x, y) => x + y // several parameters
() => DateTime.Now.Year // no parameters
(int x, string s) => s.Length > x // explicit parameter types
order => // statement lambda: a block with return
{
decimal subtotal = order.Qty * order.Price;
return subtotal > 100m ? subtotal * 0.9m : subtotal;
}
Expression lambda punya satu ekspresi setelah =>, dan nilainya menjadi hasilnya. Statement lambda punya blok di dalam kurung kurawal, yang bisa berisi pernyataan apa pun dan harus melakukan return sebuah nilai di setiap jalur kalau delegate-nya mengembalikan nilai (kalau tidak, muncul CS1643, Not all code paths return a value in lambda expression).
Versi yang lebih baru menambahkan bentuk lain, semuanya opsional:
- C# 9: parameter discard, jadi beberapa parameter yang tidak dipakai bisa semuanya ditulis
_((_, _) => Save()), dan lambdastaticyang dilarang menangkap variabel. - C# 10: "natural type", jadi
var square = (int x) => x * x;disimpulkan sebagaiFunc<int, int>. Sebelumnya,var f = x => x * 2;gagal; bahkan di C# 10 bentuk itu tetap gagal tanpa tipe parameter, dengan CS8917The delegate type could not be inferred. - C# 12: nilai default untuk parameter,
(int x, int y = 1) => x + y.
Mengirim lambda ke method
Method apa pun yang menerima parameter delegate bisa menerima lambda. Dengan cara inilah kamu mengirim perilaku, bukan hanya data:
Output:
report.pdf, scan.pdf
photo.jpg, cv.txt, scan.pdf
cv.txt, scan.pdf, photo.jpg, report.pdf
Filter tidak tahu apa arti "keep"; pemanggilnya yang menentukan lewat lambda. List.Sort bekerja dengan cara yang sama: lambda-nya adalah perbandingan yang mengembalikan negatif, nol atau positif, dan Sort memanggilnya sesering yang ia butuhkan.
Lambda di LINQ
LINQ adalah tempat sebagian besar kode C# bertemu lambda. Setiap operator query menerima satu lambda: Where sebuah kondisi, Select sebuah proyeksi, OrderBy sebuah key, Sum nilai yang akan dijumlahkan:
Output:
Ben: 250
Ana: 60
Cy: 45
Ana spent 80
Ben spent 250
Cy spent 45
Lambda-nya tidak dijalankan di tempat ia ditulis. LINQ menyimpannya dan memanggilnya saat query dienumerasi oleh foreach, ToList() atau Sum(), yang disebut deferred execution. Halaman LINQ membahas operator-operatornya.
Closure: variabel yang ditangkap
Lambda bisa memakai variabel lokal dan parameter dari method tempat ia ditulis. Lambda menangkap variabelnya, bukan nilainya saat itu, dan variabel itu tetap hidup selama lambda-nya hidup:
Output:
1
2
3
1
50.0
Setiap pemanggilan MakeCounter membuat count yang baru, jadi kedua penghitung itu tidak saling bergantung. Lambda diskon membaca rate saat dijalankan, bukan saat ditulis, jadi yang diterapkan adalah 50%. Compiler mewujudkannya dengan memindahkan variabel yang ditangkap ke sebuah class tersembunyi yang dipakai bersama oleh method dan lambda itu.
Jebakan penangkapan variabel loop
Menangkap variabel loop adalah saat closure menggigit. Perulangan for punya satu variabel untuk seluruh perulangan, jadi setiap lambda yang dibuat di dalamnya memakai variabel yang sama:
Output:
3 3 3 <- for loop, shared i
0 1 2 <- for loop, copied
0 1 2 <- foreach
Ketiga lambda di loop pertama dijalankan setelah loop selesai, saat i bernilai 3. Menyalin i ke variabel yang dideklarasikan di dalam badan loop memberi setiap lambda variabelnya sendiri. foreach sudah menangani ini dengan benar: sejak C# 5, variabel iterasinya adalah variabel baru di setiap putaran. Sebelum C# 5, ia juga punya bug yang sama, itulah sebabnya jawaban lama di internet masih menyarankan salinan ini untuk foreach juga.
Jebakan yang sama berlaku untuk apa pun yang dijalankan belakangan: event handler, timer, Task.Run, dan query LINQ yang dibuat di dalam loop lalu dienumerasi setelahnya.
Lambda, anonymous method dan local function
Lambda menggantikan sintaks anonymous method dari C# 2, yang masih bisa di-compile:
Func<int, bool> isEven = delegate (int n) { return n % 2 == 0; }; // C# 2 anonymous method
Func<int, bool> isEven2 = n => n % 2 == 0; // lambda
Untuk fungsi bantu yang hanya dipakai di dalam satu method, C# 7.0 juga menyediakan local function, yang punya nama, bisa rekursif, dan tidak mengalokasikan delegate kecuali kamu mengonversinya menjadi delegate. Pakai lambda saat kamu mengirim perilaku ke sesuatu (LINQ, Sort, sebuah event); pakai local function atau method private saat kamu memanggilnya sendiri.
Ada satu perbedaan lagi yang muncul dengan provider LINQ seperti Entity Framework: lambda yang diberikan ke Expression<Func<T, bool>> tidak di-compile menjadi kode sama sekali, melainkan menjadi struktur data yang menggambarkan kode itu, lalu provider menerjemahkannya ke SQL. Sintaksnya identik; tipe parameter dari method yang kamu panggil yang menentukan mana yang kamu dapat.
Pertanyaan yang sering diajukan
Apa itu lambda expression di C#?
Fungsi anonim yang ditulis dengan operator =>: x => x * 2 menerima x dan mengembalikan x * 2. Lambda tidak punya nama sendiri; ia dikonversi ke tipe delegate seperti Func<int, int> atau Action<string> dan bisa disimpan di variabel, dikirim ke method atau dikembalikan dari method.
Bagaimana cara menulis lambda dengan banyak parameter atau tanpa parameter?
Taruh parameternya di dalam tanda kurung: (a, b) => a + b untuk dua parameter, () => DateTime.Now untuk tanpa parameter. Hanya lambda dengan satu parameter yang boleh tidak memakai tanda kurung (x => x + 1). Kamu juga bisa menuliskan tipenya secara eksplisit: (int a, int b) => a + b.
Apa itu closure pada lambda C#?
Lambda yang memakai variabel lokal dari method di sekitarnya akan menangkap variabel itu, bukan salinan nilainya. Variabel itu hidup selama lambda-nya hidup, dan kalau salah satu sisi mengubahnya nanti, sisi lain akan melihat perubahan itu. Inilah yang membuat penghitung dan callback bisa bekerja, sekaligus penyebab bug klasik penangkapan variabel di perulangan for.
Kenapa semua lambda di perulangan for melihat nilai yang sama?
Perulangan for punya satu variabel loop untuk seluruh perulangan, jadi setiap lambda yang dibuat di dalamnya menangkap i yang sama dan melihat nilai akhirnya saat dijalankan nanti. Salin ke variabel lokal di dalam badan loop (int copy = i;) lalu tangkap salinannya. foreach tidak punya masalah ini sejak C# 5, karena ia membuat variabel baru di setiap iterasi.
Kenapa var f = x => x * 2 tidak bisa di-compile?
Compiler tidak bisa mengetahui tipe delegate yang kamu inginkan atau tipe dari x, jadi ia melaporkan CS8917 The delegate type could not be inferred (sebelum C# 10, CS0815). Deklarasikan tipenya (Func<int, int> f = x => x * 2;) atau, di C# 10 ke atas, beri tipe pada parameternya: var f = (int x) => x * 2;.