Lambda ifadesi, => operatörüyle ("gider") satır içinde yazılan küçük, anonim bir fonksiyondur. Parametreler sola, sonuç sağa yazılır:
Çıktı:
60.0
12
ORDER SHIPPED!
96.0, 180.0
Bir lambda'nın kendi tipi yoktur. Bağlamın beklediği delegate tipinde bir delegate'e (bir fonksiyonu gösteren bir nesne) dönüşür: burada Func<decimal, decimal>, List.FindAll için bir Predicate<T>, bir event için bir EventHandler. Func delegeleri bir değer döndürür, Action delegeleri döndürmez; Func ve Action sayfası ailenin tamamını listeler.
Sözdizimi Biçimleri
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;
}
Bir ifade lambda'sı =>'dan sonra tek bir ifade içerir ve o ifadenin değeri sonuçtur. Bir deyim lambda'sı süslü parantez içinde bir blok içerir; bu blok her türlü deyimi içerebilir ve delegate bir değer döndürüyorsa her yolda bir değer return etmelidir (aksi halde CS1643, Not all code paths return a value in lambda expression).
Yeni sürümler hepsi isteğe bağlı olan başka biçimler de ekledi:
- C# 9: discard parametreleri, böylece kullanılmayan birkaç parametrenin hepsi
_olabilir ((_, _) => Save()), ve değişken yakalaması yasak olanstaticlambda'lar. - C# 10: bir "doğal tip", böylece
var square = (int x) => x * x;Func<int, int>olarak çıkarılır. Ondan öncevar f = x => x * 2;başarısız olur; C# 10'da bile parametre tipi olmadan hâlâ CS8917The delegate type could not be inferredile başarısız olur. - C# 12: varsayılan parametre değerleri,
(int x, int y = 1) => x + y.
Lambda'ları Metotlara Geçirmek
Delegate parametresi alan her metot bir lambda kabul eder. Yalnızca veri değil davranış da böyle geçirilir:
Çıktı:
report.pdf, scan.pdf
photo.jpg, cv.txt, scan.pdf
cv.txt, scan.pdf, photo.jpg, report.pdf
Filter "tutmak"ın ne anlama geldiğini bilmez; buna çağıran bir lambda ile karar verir. List.Sort da aynı şekilde çalışır: lambda negatif, sıfır ya da pozitif döndüren bir karşılaştırmadır ve Sort onu gerektiği kadar çağırır.
LINQ'te Lambda'lar
Çoğu C# kodunun lambda'larla karşılaştığı yer LINQ'tir. Her sorgu operatörü bir tane alır: Where bir koşul, Select bir projeksiyon, OrderBy bir anahtar, Sum toplanacak bir değer:
Çıktı:
Ben: 250
Ana: 60
Cy: 45
Ana spent 80
Ben spent 250
Cy spent 45
Lambda'lar yazıldıkları yerde çalışmaz. LINQ onları saklar ve sorgu foreach, ToList() ya da Sum() ile dolaşıldığında çağırır; buna ertelenmiş yürütme denir. LINQ sayfası operatörlerin kendisini ele alır.
Closure'lar: Yakalanan Değişkenler
Bir lambda, içinde yazıldığı metodun yerel değişkenlerini ve parametrelerini kullanabilir. Geçerli değerini değil değişkeni yakalar ve değişken lambda yaşadığı sürece yaşamaya devam eder:
Çıktı:
1
2
3
1
50.0
Her MakeCounter çağrısı yeni bir count oluşturur, bu yüzden iki sayaç birbirinden bağımsızdır. İndirim lambda'sı rate'i yazıldığında değil çalıştığında okudu, bu yüzden %50 uyguladı. Derleyici bunu, yakalanan değişkenleri metodun ve lambda'nın paylaştığı gizli bir sınıfa taşıyarak uygular.
Döngü Yakalama Tuzağı
Bir döngü değişkenini yakalamak closure'ların ısırdığı yerdir. Bir for döngüsünün tüm döngü için tek bir değişkeni vardır, bu yüzden içinde oluşturulan her lambda o değişkeni paylaşır:
Çıktı:
3 3 3 <- for loop, shared i
0 1 2 <- for loop, copied
0 1 2 <- foreach
İlk döngüdeki üç lambda da döngü bittikten sonra, i 3 iken çalıştı. i'yi gövdenin içinde bildirilen bir değişkene kopyalamak her lambda'ya kendi değişkenini verir. foreach bunu kendiliğinden doğru yapar: C# 5'ten beri yineleme değişkeni her geçişte yeni bir değişkendir. C# 5'ten önce aynı hataya sahipti; internetteki eski cevapların foreach için de kopyayı önermesinin nedeni budur.
Aynı tuzak sonradan çalışan her şey için geçerlidir: event handler'lar, timer'lar, Task.Run ve bir döngüde oluşturulup ondan sonra dolaşılan LINQ sorguları.
Lambda'lar, Anonim Metotlar ve Yerel Fonksiyonlar
Lambda'lar, hâlâ derlenen C# 2 anonim metot sözdiziminin yerini aldı:
Func<int, bool> isEven = delegate (int n) { return n % 2 == 0; }; // C# 2 anonymous method
Func<int, bool> isEven2 = n => n % 2 == 0; // lambda
Yalnızca tek bir metodun içinde kullanılan bir yardımcı için C# 7.0 ayrıca yerel fonksiyonlar sunar; bunların bir adı vardır, özyinelemeli olabilirler ve siz onları bir delegate'e dönüştürmedikçe delegate ayırmazlar. Bir şeye (LINQ, Sort, bir event) davranış geçirirken lambda kullanın; onu kendiniz çağırırken yerel bir fonksiyon ya da private bir metot kullanın.
Entity Framework gibi LINQ sağlayıcılarında bir ayrım daha ortaya çıkar: Expression<Func<T, bool>>'a atanan bir lambda hiç koda derlenmez; sağlayıcının SQL'e çevirdiği, kodu tanımlayan bir veri yapısına derlenir. Sözdizimi aynıdır; hangisini alacağınıza çağırdığınız metodun parametre tipi karar verir.
Sıkça Sorulan Sorular
C#'ta lambda ifadesi nedir?
=> operatörüyle yazılmış anonim bir fonksiyondur: x => x * 2, x'i alır ve x * 2 döndürür. Bir lambda'nın kendi adı yoktur; Func<int, int> ya da Action<string> gibi bir delegate tipine dönüştürülür ve bir değişkende saklanabilir, bir metoda geçirilebilir ya da bir metottan döndürülebilir.
Birden fazla parametreli ya da parametresiz bir lambda nasıl yazılır?
Parametreleri parantez içine alın: iki parametre için (a, b) => a + b, hiç parametre yoksa () => DateTime.Now. Parantezleri yalnızca tek bir parametre atabilir (x => x + 1). Tipleri açıkça da verebilirsiniz: (int a, int b) => a + b.
C# lambda'sında closure nedir?
Çevresindeki metottan yerel bir değişken kullanan bir lambda, değerinin anlık görüntüsünü değil o değişkeni yakalar. Değişken lambda yaşadığı sürece yaşar ve iki taraftan biri onu sonradan değiştirirse diğeri değişikliği görür. Sayaçları ve callback'leri çalıştıran budur; klasik for döngüsü yakalama hatasına yol açan da budur.
Bir for döngüsündeki lambda'lar neden hep aynı değeri görüyor?
Bir for döngüsünün tüm döngü için tek bir döngü değişkeni vardır, bu yüzden içinde oluşturulan her lambda aynı i'yi yakalar ve sonradan çalıştığında son değerini görür. Onu gövdenin içinde yerel bir değişkene kopyalayın (int copy = i;) ve kopyayı yakalayın. foreach'te C# 5'ten beri bu sorun yoktur, çünkü her yineleme için taze bir değişken oluşturur.
var f = x => x * 2 neden derlenmiyor?
Derleyici hangi delegate tipini istediğinizi ya da x'in tipinin ne olduğunu bilemez, bu yüzden CS8917 The delegate type could not be inferred (C# 10'dan önce CS0815) bildirir. Tipi bildirin (Func<int, int> f = x => x * 2;) ya da C# 10 ve sonrasında parametrenin tipini yazın: var f = (int x) => x * 2;.