Lambda ifadeleri ve anonim işlevler
Anonim bir işlev oluşturmak için lambda ifadesi kullanırsınız. Lambda'nın parametre listesini gövdesinden ayırmak için lambda bildirim işlecini =>
kullanın. Lambda ifadesi aşağıdaki iki biçimden herhangi biri olabilir:
İfade lambda, gövdesi olarak bir ifadeye sahip olan:
(input-parameters) => expression
Gövdesi bir deyim bloğuna sahip olan lambda ifadesi:
(input-parameters) => { <sequence-of-statements> }
Lambda ifadesi oluşturmak için lambda işlecinin sol tarafında giriş parametreleri (varsa) ve diğer tarafta bir ifade veya deyim bloğu belirtirsiniz.
Herhangi bir lambda ifadesi, temsilci türüne dönüştürülebilir. Parametrelerinin ve dönüş değerinin türleri, lambda ifadesinin dönüştürülebileceği temsilci türünü tanımlar. Lambda ifadesi bir değer döndürmezse, Action
temsilci türlerinden birine dönüştürülebilir; aksi takdirde, Func
temsilci türlerinden birine dönüştürülebilir. Örneğin, iki parametresi olan ve değer döndürmeyen bir lambda ifadesi Action<T1,T2> temsilcisine dönüştürülebilir. Bir parametresi olan ve değer döndüren lambda ifadesi Func<T,TResult> temsilcisine dönüştürülebilir. Aşağıdaki örnekte, x
adlı bir parametreyi belirten ve x
kare değerini döndüren x => x * x
lambda ifadesi, temsilci türünde bir değişkene atanır:
Func<int, int> square = x => x * x;
Console.WriteLine(square(5));
// Output:
// 25
İfade lambdaları, aşağıdaki örnekte gösterildiği gibi ifade ağacı türlerine de dönüştürülebilir:
System.Linq.Expressions.Expression<Func<int, int>> e = x => x * x;
Console.WriteLine(e);
// Output:
// x => (x * x)
Temsilci türlerinin veya ifade ağaçlarının örneklerini gerektiren herhangi bir kodda lambda ifadeleri kullanırsınız. Bir örnek, arka planda yürütülmesi gereken kodu geçirmek için Task.Run(Action) yönteminin bağımsız değişkenidir. Aşağıdaki örnekte gösterildiği gibi C#
int[] numbers = { 2, 3, 4, 5 };
var squaredNumbers = numbers.Select(x => x * x);
Console.WriteLine(string.Join(" ", squaredNumbers));
// Output:
// 4 9 16 25
System.Linq.Enumerable sınıfındaki Enumerable.Select yöntemini çağırmak için yöntem tabanlı söz dizimi kullandığınızda (örneğin LINQ to Objects ve LINQ to XML) parametresi, System.Func<T,TResult>bir temsilci türüdür.
System.Linq.Queryable sınıfında, örneğin LINQ to SQL'de Queryable.Select yöntemini çağırdığınızda, parametre türü Expression<Func<TSource,TResult>>
bir ifade ağacı türüdür. Her iki durumda da parametre değerini belirtmek için aynı lambda ifadesini kullanabilirsiniz. Bu, lambdalardan oluşturulan nesnelerin türü farklı olsa da iki Select
çağrısının benzer görünmesini sağlar.
İfade lambdaları
(input-parameters) => expression
Lambda ifadesinin gövdesi bir yöntem çağrısından oluşabilir. Ancak, SQL Server gibi .NET Ortak Dil Çalışma Zamanı (CLR) bağlamı dışında değerlendirilen ifade ağaçları oluşturuyorsanız, lambda ifadelerinde yöntem çağrılarını kullanmamalısınız. Yöntemlerin .NET Ortak Dil Çalışma Zamanı (CLR) bağlamının dışında bir anlamı yoktur.
Deyim lambdaları
Lambda deyimi, ifadeleri küme ayraçları içine alınmış olması dışında bir ifade lambdasına benzer:
(input-parameters) => { <sequence-of-statements> }
Lambda deyiminin gövdesi herhangi bir sayıda deyimden oluşabilir; ancak pratikte genellikle iki veya üçten fazla yoktur.
Action<string> greet = name =>
{
string greeting = $"Hello {name}!";
Console.WriteLine(greeting);
};
greet("World");
// Output:
// Hello World!
İfade ağaçları oluşturmak için lambdas deyimini kullanamazsınız.
Lambda ifadesinin giriş parametreleri
Lambda ifadesinin giriş parametrelerini parantez içine alırsınız. Boş ayraçlarla sıfır giriş parametresi belirtin:
Action line = () => Console.WriteLine();
Lambda ifadesinde yalnızca bir giriş parametresi varsa parantezler isteğe bağlıdır:
Func<double, double> cube = x => x * x * x;
İki veya daha fazla giriş parametresi virgülle ayrılır:
Func<int, int, bool> testForEquality = (x, y) => x == y;
Bazen derleyici giriş parametresi türlerini çıkaramaz. Türleri aşağıdaki örnekte gösterildiği gibi açıkça belirtebilirsiniz:
Func<int, string, bool> isTooLong = (int x, string s) => s.Length > x;
Giriş parametresi türlerinin tümü açık veya tamamen örtük olmalıdır; aksi takdirde, CS0748 derleyici hatası oluşur.
İfadede kullanılmayan bir lambda ifadesinin iki veya daha fazla giriş parametresini belirtmek için atma kullanabilirsiniz:
Func<int, int, int> constant = (_, _) => 42;
Lambda kullanılmayan parametreler, bir lambda ifadesi kullanarakbir olay işleyicisi sağladığınızda yararlı olabilir.
Not olarak dikkate alınız
Geriye dönük uyumluluk için, _
tek bir giriş parametresi adlandırılmışsa, lambda ifadesinde _
bu parametrenin adı olarak değerlendirilir.
C# 12 ile başlayarak, lambda ifadelerindeki parametreler için
var IncrementBy = (int source, int increment = 1) => source + increment;
Console.WriteLine(IncrementBy(5)); // 6
Console.WriteLine(IncrementBy(5, 2)); // 7
Ayrıca, params
dizileri veya koleksiyonları olan lambda ifadelerini parametre olarak bildirebilirsiniz:
var sum = (params IEnumerable<int> values) =>
{
int sum = 0;
foreach (var value in values)
sum += value;
return sum;
};
var empty = sum();
Console.WriteLine(empty); // 0
var sequence = new[] { 1, 2, 3, 4, 5 };
var total = sum(sequence);
Console.WriteLine(total); // 15
Bu güncelleştirmelerin bir parçası olarak, varsayılan parametresi olan bir yöntem grubu bir lambda ifadesine atandığında, bu lambda ifadesi de aynı varsayılan parametreye sahiptir.
params
koleksiyon parametresine sahip bir yöntem grubu da lambda ifadesine atanabilir.
Varsayılan parametrelere veya params
koleksiyonlara sahip Lambda ifadelerinin Func<>
veya Action<>
türlerine karşılık gelen doğal türleri yoktur. Ancak, varsayılan parametre değerlerini içeren temsilci türleri tanımlayabilirsiniz:
delegate int IncrementByDelegate(int source, int increment = 1);
delegate int SumDelegate(params int[] values);
delegate int SumCollectionDelegate(params IEnumerable<int> values);
Alternatif olarak, temsilci türünü tanımlamak için var
bildirimleriyle örtük olarak yazılan değişkenleri de kullanabilirsiniz. Derleyici doğru temsilci türünü sentezler.
Lambda ifadelerindeki varsayılan parametreler hakkında daha fazla bilgi için, lambda ifadelerindeki varsayılan parametreler için özellik belirtimine bkz..
Zaman uyumsuz lambdalar
async ve await anahtar sözcüklerini kullanarak, zaman uyumsuz işlemeyi içeren lambda ifadelerini ve deyimlerini kolayca oluşturabilirsiniz. Örneğin, aşağıdaki Windows Forms örneği, ExampleMethodAsync
zaman uyumsuz bir yöntemi çağıran ve bekleyen bir olay işleyicisi içerir.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
button1.Click += button1_Click;
}
private async void button1_Click(object sender, EventArgs e)
{
await ExampleMethodAsync();
textBox1.Text += "\r\nControl returned to Click event handler.\n";
}
private async Task ExampleMethodAsync()
{
// The following line simulates a task-returning asynchronous process.
await Task.Delay(1000);
}
}
Asenkron lambda kullanarak aynı olay işleyicisini ekleyebilirsiniz. Bu işleyiciyi eklemek için aşağıdaki örnekte gösterildiği gibi lambda parametre listesinden önce bir async
değiştirici ekleyin:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
button1.Click += async (sender, e) =>
{
await ExampleMethodAsync();
textBox1.Text += "\r\nControl returned to Click event handler.\n";
};
}
private async Task ExampleMethodAsync()
{
// The following line simulates a task-returning asynchronous process.
await Task.Delay(1000);
}
}
Zaman uyumsuz yöntemleri oluşturma ve kullanma hakkında daha fazla bilgi için bkz. Zaman Uyumsuz Programlama ile zaman uyumsuz programlama ve bekleme.
Lambda ifadeleri ve tuple'lar
C# dili,
Bileşenlerinin virgülle ayrılmış listesini parantez içine alarak bir demet tanımlarsınız. Aşağıdaki örnek, bir lambda ifadesine sayı dizisi geçirmek için üç bileşenli tanımlama grubu kullanır. Bu da her değeri iki katına çıkartır ve çarpmaların sonucunu içeren üç bileşenli bir tanımlama grubu döndürür.
Func<(int, int, int), (int, int, int)> doubleThem = ns => (2 * ns.Item1, 2 * ns.Item2, 2 * ns.Item3);
var numbers = (2, 3, 4);
var doubledNumbers = doubleThem(numbers);
Console.WriteLine($"The set {numbers} doubled: {doubledNumbers}");
// Output:
// The set (2, 3, 4) doubled: (4, 6, 8)
Normalde, bir demetin alanları Item1
, Item2
, vs. olarak adlandırılır. Ancak, aşağıdaki örnekte olduğu gibi, adlandırılmış bileşenlere sahip bir tuple tanımlayabilirsiniz.
Func<(int n1, int n2, int n3), (int, int, int)> doubleThem = ns => (2 * ns.n1, 2 * ns.n2, 2 * ns.n3);
var numbers = (2, 3, 4);
var doubledNumbers = doubleThem(numbers);
Console.WriteLine($"The set {numbers} doubled: {doubledNumbers}");
C# tuple'ları hakkında daha fazla bilgi için bkz. Tuple türleri.
Standart sorgu işleçlerine sahip lambdalar
LINQ to Objects, diğer uygulamalarla birlikte, türü Func<TResult> genel temsilci ailesinden biri olan bir giriş parametresine sahiptir. Bu temsilciler, giriş parametrelerinin sayısını ve türünü ve temsilcinin dönüş türünü tanımlamak için tür parametrelerini kullanır.
Func
temsilcileri, bir kaynak veri kümesindeki her öğeye uygulanan kullanıcı tanımlı ifadeleri kapsüllemek için kullanışlıdır. Örneğin, Func<T,TResult> temsilci türünü göz önünde bulundurun:
public delegate TResult Func<in T, out TResult>(T arg)
Temsilci, int
bir giriş parametresi ve bool
dönüş değeri olduğu bir Func<int, bool>
örneği olarak örneklenebilir. Dönüş değeri her zaman son tür parametresinde belirtilir. Örneğin Func<int, string, bool>
, int
ve string
olmak üzere iki giriş parametresi ve bool
dönüş türüne sahip bir temsilci tanımlar. Aşağıdaki Func
temsilcisi çağrıldığında, giriş parametresinin beşe eşit olup olmadığını gösteren Boole değeri döndürür:
Func<int, bool> equalsFive = x => x == 5;
bool result = equalsFive(4);
Console.WriteLine(result); // False
Bağımsız değişken türü bir Expression<TDelegate>olduğunda, örneğin Queryable türünde tanımlanan standart sorgu işleçlerinde bir lambda ifadesi de sağlayabilirsiniz. bir Expression<TDelegate> bağımsız değişkeni belirttiğinizde, lambda bir ifade ağacına derlenmiş olur.
Aşağıdaki örnekte standart Count sorgu işleci kullanılır:
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
int oddNumbers = numbers.Count(n => n % 2 == 1);
Console.WriteLine($"There are {oddNumbers} odd numbers in {string.Join(" ", numbers)}");
Derleyici giriş parametresinin türünü çıkarsayabileceği gibi, bunu açıkça da belirtebilirsiniz. Bu özel lambda ifadesi, ikiye bölündüğünde 1'in geri kalanına sahip olan bu tamsayıları (n
) sayar.
Aşağıdaki örnek, 9'un önündeki numbers
dizisindeki tüm öğeleri içeren bir dizi oluşturur çünkü bu, dizideki koşulu karşılamayan ilk sayıdır:
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
var firstNumbersLessThanSix = numbers.TakeWhile(n => n < 6);
Console.WriteLine(string.Join(" ", firstNumbersLessThanSix));
// Output:
// 5 4 1 3
Aşağıdaki örnek, birden çok giriş parametresini parantez içine alarak belirtir. yöntemi, değeri dizideki sıra konumundan küçük bir sayı bulana kadar numbers
dizisindeki tüm öğeleri döndürür:
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
var firstSmallNumbers = numbers.TakeWhile((n, index) => n >= index);
Console.WriteLine(string.Join(" ", firstSmallNumbers));
// Output:
// 5 4
lambda ifadelerini
var numberSets = new List<int[]>
{
new[] { 1, 2, 3, 4, 5 },
new[] { 0, 0, 0 },
new[] { 9, 8 },
new[] { 1, 0, 1, 0, 1, 0, 1, 0 }
};
var setsWithManyPositives =
from numberSet in numberSets
where numberSet.Count(n => n > 0) > 3
select numberSet;
foreach (var numberSet in setsWithManyPositives)
{
Console.WriteLine(string.Join(" ", numberSet));
}
// Output:
// 1 2 3 4 5
// 1 0 1 0 1 0 1 0
Lambda ifadelerinde tür çıkarımı
Lambda yazarken, derleyici türü lambda gövdesine, parametre türlerine ve C# dil belirtiminde açıklandığı gibi diğer faktörlere göre çıkarabildiğinden genellikle giriş parametreleri için bir tür belirtmeniz gerekmez. Standart sorgu işleçlerinin çoğu için ilk giriş, kaynak dizideki öğelerin türüdür. bir IEnumerable<Customer>
sorgularsanız, giriş değişkeninin Customer
nesnesi olduğu sonucuna varılır ve bu da yöntemlerine ve özelliklerine erişiminiz olduğu anlamına gelir:
customers.Where(c => c.City == "London");
Lambdalar için tür çıkarımı için genel kurallar şunlardır:
- Lambda, temsilci türüyle aynı sayıda parametre içermelidir.
- Lambda'daki her giriş parametresi, ilgili temsilci parametresine örtük olarak dönüştürülebilir olmalıdır.
- Lambdanın dönüş değeri (varsa) temsilcinin dönüş türüne örtük olarak dönüştürülebilir olmalıdır.
Lambda ifadesinin doğal türü
Ortak tür sisteminde iç "lambda ifadesi" kavramı olmadığından, bir lambda ifadesinin türü yoktur. Ancak, bazen bir lambda ifadesinin "türünden" resmi olmayan bir şekilde konuşmak uygundur. Bu gayri resmi "tür", lambda ifadesinin dönüştürüldüğü temsilci türüne veya Expression türüne atıfta bulunur.
Lambda ifadesinde doğal türolabilir. Sizi lambda ifadesi için Func<...>
veya Action<...>
gibi bir temsilci türü bildirmeye zorlamak yerine, derleyici lambda ifadesinden temsilci türünü çıkarsayabilir. Örneğin, aşağıdaki bildirimi göz önünde bulundurun:
var parse = (string s) => int.Parse(s);
Derleyici, parse
'ın Func<string, int>
olduğunu çıkarsayabilir. Derleyici, uygun bir tanesi varsa, mevcut Func
veya Action
temsilcisini seçer. Aksi takdirde, bir temsilci türünü sentezler. Örneğin, lambda ifadesinde ref
parametreleri varsa temsilci türü sentezlenmiş olur. Lambda ifadesi doğal bir türe sahip olduğunda, System.Object veya System.Delegategibi daha az açık bir türe atanabilir:
object parse = (string s) => int.Parse(s); // Func<string, int>
Delegate parse = (string s) => int.Parse(s); // Func<string, int>
Tam olarak bir aşırı yüke sahip yöntem gruplarının (parametre listeleri olmayan yöntem adları) doğal bir türü vardır:
var read = Console.Read; // Just one overload; Func<int> inferred
var write = Console.Write; // ERROR: Multiple overloads, can't choose
System.Linq.Expressions.LambdaExpressionveya System.Linq.Expressions.Expressionbir lambda ifadesi atarsanız ve lambda doğal bir temsilci türüne sahipse, ifadenin doğal bir System.Linq.Expressions.Expression<TDelegate>türü vardır ve tür parametresi için bağımsız değişken olarak doğal temsilci türü kullanılır:
LambdaExpression parseExpr = (string s) => int.Parse(s); // Expression<Func<string, int>>
Expression parseExpr = (string s) => int.Parse(s); // Expression<Func<string, int>>
Tüm lambda ifadelerinin doğal bir türü yoktur. Aşağıdaki bildirimi göz önünde bulundurun:
var parse = s => int.Parse(s); // ERROR: Not enough type info in the lambda
Derleyici, s
için bir parametre türü çıkaramaz. Derleyici doğal bir tür çıkaramıyorsa, türünü bildirmeniz gerekir:
Func<string, int> parse = s => int.Parse(s);
Açık dönüş türü
Genellikle bir lambda ifadesinin dönüş türü açıktır ve çıkarılır. Çalışmayan bazı ifadeler için:
var choose = (bool b) => b ? 1 : "two"; // ERROR: Can't infer return type
Giriş parametrelerinden önce lambda ifadesinin dönüş türünü belirtebilirsiniz. Açık bir dönüş türü belirttiğinizde, giriş parametrelerini parantez içinde kullanmanız gerekir:
var choose = object (bool b) => b ? 1 : "two"; // Func<bool, object>
Öznitelik
Lambda ifadesine ve parametrelerine öznitelikler ekleyebilirsiniz. Aşağıdaki örnekte bir lambda ifadesine özniteliklerin nasıl ekleneceği gösterilmektedir:
Func<string?, int?> parse = [ProvidesNullCheck] (s) => (s is not null) ? int.Parse(s) : null;
Aşağıdaki örnekte gösterildiği gibi giriş parametrelerine veya dönüş değerine öznitelikler de ekleyebilirsiniz:
var concat = ([DisallowNull] string a, [DisallowNull] string b) => a + b;
var inc = [return: NotNullIfNotNull(nameof(s))] (int? s) => s.HasValue ? s++ : null;
Yukarıdaki örneklerde gösterildiği gibi, bir lambda ifadesine veya parametrelerine öznitelik eklerken giriş parametrelerini parantez içine eklemeniz gerekir.
Önemli
Lambda ifadeleri, arka planda kullanılan temsilci türü aracılığıyla çağrılır. Bu yöntemlerden ve yerel işlevlerden farklıdır. Temsilcinin Invoke
yöntemi lambda ifadesinde öznitelikleri denetlemez. Lambda ifadesi çağrıldığında özniteliklerin hiçbir etkisi olmaz. Lambda ifadelerindeki öznitelikler kod analizi için yararlıdır ve yansıma yoluyla bulunabilir. Bu kararın bir sonucu, System.Diagnostics.ConditionalAttribute bir lambda ifadesine uygulanamamasıdır.
Lambda ifadelerinde dış değişkenlerin ve değişken kapsamının yakalanması
Lambdalar dış değişkenlerebaşvurabilir. Bu dış değişkenler, lambda ifadesini tanımlayan yöntemin kapsamında veya lambda ifadesini içeren türün kapsamında olan değişkenlerdir. Bu şekilde yakalanan değişkenler, aksi takdirde kapsamın dışına çıksa ve çöp toplansa bile lambda ifadesinde kullanılmak üzere depolanır. Dış değişkenin bir lambda ifadesinde kullanılmadan önce kesin olarak atanmış olması gerekir. Aşağıdaki örnekte bu kurallar gösterilmektedir:
public static class VariableScopeWithLambdas
{
public class VariableCaptureGame
{
internal Action<int>? updateCapturedLocalVariable;
internal Func<int, bool>? isEqualToCapturedLocalVariable;
public void Run(int input)
{
int j = 0;
updateCapturedLocalVariable = x =>
{
j = x;
bool result = j > input;
Console.WriteLine($"{j} is greater than {input}: {result}");
};
isEqualToCapturedLocalVariable = x => x == j;
Console.WriteLine($"Local variable before lambda invocation: {j}");
updateCapturedLocalVariable(10);
Console.WriteLine($"Local variable after lambda invocation: {j}");
}
}
public static void Main()
{
var game = new VariableCaptureGame();
int gameInput = 5;
game.Run(gameInput);
int jTry = 10;
bool result = game.isEqualToCapturedLocalVariable!(jTry);
Console.WriteLine($"Captured local variable is equal to {jTry}: {result}");
int anotherJ = 3;
game.updateCapturedLocalVariable!(anotherJ);
bool equalToAnother = game.isEqualToCapturedLocalVariable(anotherJ);
Console.WriteLine($"Another lambda observes a new value of captured variable: {equalToAnother}");
}
// Output:
// Local variable before lambda invocation: 0
// 10 is greater than 5: True
// Local variable after lambda invocation: 10
// Captured local variable is equal to 10: True
// 3 is greater than 5: False
// Another lambda observes a new value of captured variable: True
}
Lambda ifadelerindeki değişken kapsamı için aşağıdaki kurallar geçerlidir:
- Yakalanmış bir değişken, onu referans alan temsilci çöp toplama için uygun hale gelene kadar çöp toplanmaz.
- Lambda ifadesi içinde tanımlanan değişkenler, kapsayan fonksiyonda görünmez.
- Lambda ifadesi, kapsayan yöntemden,içindeki bir
'ü,'teki bir ref veya parametresindeki bir out'u doğrudan yakalayamaz. - Bir lambda ifadesindeki return ifadesi, kapsayan yöntemin dönmesine sebep olmaz.
- Lambda ifadesi, atlama deyiminin hedefi lambda ifade bloğunun dışındaysa, goto, breakveya continue ifadesi içeremez. Hedef blok içindeyse, lambda ifade bloğunun dışında bir atlama deyimi bulunması da hatadır.
Lambda tarafından yerel değişkenlerin veya örnek durumunun yanlışlıkla yakalanmasını önlemek için bir lambda ifadesine static
değiştiricisini uygulayabilirsiniz:
Func<double, double> square = static x => x * x;
Statik lambda, çevreleyen kapsamların yerel değişkenlerini veya örnek durumunu yakalayamaz, ancak statik üyelere ve sabit tanımlara başvurabilir.
C# dil belirtimi
Daha fazla bilgi için
Bu özellikler hakkında daha fazla bilgi için aşağıdaki özellik teklifi notlarını inceleyin:
- Lambda'nın kullanılmayan parametreleri
- Statik anonim işlevler
- Lambda geliştirmeleri