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:

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. Lambda ifadesinin dönüştürülebileceği temsilci türü, parametrelerinin ve dönüş değerinin türleri tarafından tanımlanır. Lambda ifadesi bir değer döndürmezse, temsilci türlerinden Action birine dönüştürülebilir; aksi takdirde, temsilci türlerinden Func birine dönüştürülebilir. Örneğin, iki parametresi olan ve değer döndüren bir lambda ifadesi temsilciye Action<T1,T2> dönüştürülemez. Bir parametresi olan ve değer döndüren lambda ifadesi temsilciye Func<T,TResult> dönüştürülebilir. Aşağıdaki örnekte, adlı x bir parametreyi belirten ve kare değerini x döndüren lambda ifadesix => x * x, 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)

Lambda ifadelerini, temsilci türlerinin veya ifade ağaçlarının örneklerini gerektiren herhangi bir kodda kullanabilirsiniz; örneğin, arka planda yürütülmesi gereken kodu geçirme yöntemine bağımsız değişken Task.Run(Action) olarak. Aşağıdaki örnekte gösterildiği gibi C# dilinde LINQ yazarken lambda ifadelerini de kullanabilirsiniz:

int[] numbers = { 2, 3, 4, 5 };
var squaredNumbers = numbers.Select(x => x * x);
Console.WriteLine(string.Join(" ", squaredNumbers));
// Output:
// 4 9 16 25

örneğin LINQ to Objects ve LINQ to XML gibi sınıfındaki System.Linq.Enumerable yöntemini çağırmak Enumerable.Select için yöntem tabanlı söz dizimi kullandığınızda, parametresi bir temsilci türüdürSystem.Func<T,TResult>. sınıfındaki Queryable.SelectSystem.Linq.Queryable yöntemini çağırdığınızda (örneğin LINQ to SQL'de) parametre türü bir ifade ağacı türüdür Expression<Func<TSource,TResult>>. 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ının benzer görünmesini sağlar.

İfade lambdaları

İşlecin sağ tarafında => ifadesi olan lambda ifadesi, ifade lambda olarak adlandırılır. Bir lambda ifadesi, ifadenin sonucunu verir ve aşağıdaki temel biçimi alır:

(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> }

Bir lambda deyiminin gövdesi herhangi bir sayıda deyimden oluşabilir; ancak, uygulamada genellikle iki veya üçten fazla değildir.

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ş parametrelerini 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, bir CS0748 derleyici hatası oluşur.

bir lambda ifadesinin ifadede kullanılmayan iki veya daha fazla giriş parametresini belirtmek için atmaları kullanabilirsiniz:

Func<int, int, int> constant = (_, _) => 42;

Lambda atma parametreleri, bir olay işleyicisi sağlamak için lambda ifadesi kullandığınızda yararlı olabilir.

Not

Geriye dönük uyumluluk için, yalnızca tek bir giriş parametresi olarak adlandırılmışsa _, bir lambda ifadesi _ içinde bu parametrenin adı olarak değerlendirilir.

C# 12'de başlayarak lambda ifadelerindeki parametreler için varsayılan değerler sağlayabilirsiniz. Varsayılan parametre değerlerine yönelik söz dizimi ve kısıtlamalar, yöntemler ve yerel işlevlerle aynıdır. Aşağıdaki örnekte varsayılan parametreye sahip bir lambda ifadesi bildirildikten sonra varsayılan parametreyi kullanarak bir kez ve iki açık parametreyle bir kez çağrılır:

var IncrementBy = (int source, int increment = 1) => source + increment;

Console.WriteLine(IncrementBy(5)); // 6
Console.WriteLine(IncrementBy(5, 2)); // 7

Ayrıca, dizileri olan params lambda ifadelerini parametre olarak da bildirebilirsiniz:

var sum = (params 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. Bir lambda ifadesine dizi parametresine sahip params bir yöntem grubu da atanabilir.

Varsayılan parametreleri veya params dizileri parametre olarak içeren Lambda ifadelerinin veya Action<> türlerine Func<> karşılık gelen doğal türler 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);

Alternatif olarak, temsilci türünü tanımlamak için bildirimlerle var örtük olarak yazılan değişkenleri de kullanabilirsiniz. Derleyici doğru temsilci türünü sentezler.

Hakkında daha fazla bilgi için bkz. lambda ifadelerindeki varsayılan parametreler için özellik belirtimi.

Zaman uyumsuz lambdalar

Zaman uyumsuz işlemeyi içeren lambda ifadelerini ve deyimlerini zaman uyumsuzve await anahtar sözcüklerini kullanarak kolayca oluşturabilirsiniz. Örneğin, aşağıdaki Windows Forms örneği, zaman uyumsuz bir yöntemi çağıran ve bekleyen bir olay işleyicisi içerir. ExampleMethodAsync

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);
    }
}

Zaman uyumsuz 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 ve await ile Zaman Uyumsuz Programlama.

Lambda ifadeleri ve tanımlama kümeleri

C# dili, tanımlama kümeleri için yerleşik destek sağlar. Lambda ifadesine bağımsız değişken olarak bir tanımlama grubu sağlayabilirsiniz ve lambda ifadeniz de bir tanımlama grubu döndürebilir. Bazı durumlarda, C# derleyicisi tanımlama grubu bileşenlerinin türlerini belirlemek için tür çıkarımı kullanır.

Bileşenlerinin virgülle ayrılmış listesini parantez içine alarak bir tanımlama grubu 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 tanımlama grubunun alanları , Item2, vb. olarak adlandırılırItem1. Ancak, aşağıdaki örnekte olduğu gibi adlandırılmış bileşenlerle bir tanımlama grubu 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# tanımlama kümeleri hakkında daha fazla bilgi için bkz . Tanımlama grubu türleri.

Standart sorgu işleçlerine sahip lambdalar

LINQ to Objects, diğer uygulamalar arasında türü genel temsilciler ailesinden biri Func<TResult> 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 temsilciler, bir kaynak veri kümesindeki her öğeye uygulanan kullanıcı tanımlı ifadeleri kapsüllemek için kullanışlıdır. Örneğin, temsilci türünü göz önünde bulundurun Func<T,TResult> :

public delegate TResult Func<in T, out TResult>(T arg)

Temsilci, giriş parametresi olan ve bool dönüş değeri olan bir Func<int, bool> örnek int olarak örnek olarak örneklenebilir. Dönüş değeri her zaman son tür parametresinde belirtilir. Örneğin, Func<int, string, bool> iki giriş parametresi int olan bir temsilci ve stringdönüş türü booltanımlar. Aşağıdaki Func temsilci ç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

Ayrıca, bağımsız değişken türü bir Expression<TDelegate>olduğunda bir lambda ifadesi de sağlayabilirsiniz. Örneğin, türünde tanımlanan standart sorgu işleçlerinde Queryable . Bir Expression<TDelegate> bağımsız değişken belirttiğinizde, lambda bir ifade ağacına derlenmiş olur.

Aşağıdaki örnekte standart sorgu işleci kullanılır Count :

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ü çıkarabilir veya bunu açıkça belirtebilirsiniz. Bu özel lambda ifadesi, ikiye bölündüğünde 1'in geri kalanına sahip olan tamsayıları (n) sayar.

Aşağıdaki örnek, dizideki 9'un numbers önündeki 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 numbers küçük bir sayı bulana kadar dizideki 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 doğrudan sorgu ifadelerinde kullanmazsınız, ancak aşağıdaki örnekte gösterildiği gibi bunları sorgu ifadeleri içindeki yöntem çağrılarında kullanabilirsiniz:

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ğunda ilk giriş kaynak dizisindeki öğelerin türüdür. bir IEnumerable<Customer>sorgularsanız, giriş değişkeni bir Customer nesne olarak çıkarı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.
  • Lambdadaki her giriş parametresi, denk gelen temsilci parametresine dolaylı olarak dönüştürülebilir olmalıdır.
  • Lambdanın (varsa) dönüş değeri örtük olarak temsilcinin dönüş türüne 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 resmi olmayan "tür", lambda ifadesinin dönüştürüldüğü temsilci türüne veya Expression türüne başvurur.

C# 10 ile başlayarak, lambda ifadesinin doğal bir türü olabilir. Derleyici, lambda ifadesi gibi Func<...>Action<...> bir temsilci türünü bildirmeye zorlamak yerine lambda ifadesinden temsilci türünü çıkarsayabilir. Örneğin, aşağıdaki bildirimi ele alalım:

var parse = (string s) => int.Parse(s);

Derleyici parse bir Func<string, int>olabilir. Derleyici, uygun bir tane varsa, kullanılabilir Func veya Action temsilciyi seçer. Aksi takdirde, bir temsilci türünü sentezler. Örneğin, lambda ifadesinde ref parametreler varsa temsilci türü sentezlenmiş olur. Bir lambda ifadesi doğal bir türe sahip olduğunda, veya System.Delegategibi System.Object 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

öğesine System.Linq.Expressions.Expressionbir lambda ifadesi System.Linq.Expressions.LambdaExpressionatarsanız ve lambda doğal bir temsilci türüne sahipse, ifadenin System.Linq.Expressions.Expression<TDelegate>doğal 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 için sbir 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

C# 10'da başlayarak, 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>

Özellikler

C# 10'da başlayarak, bir 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, temel alınan 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, bir lambda ifadesine uygulanamamasıdır System.Diagnostics.ConditionalAttribute .

Lambda ifadelerinde dış değişkenlerin ve değişken kapsamının yakalanması

Lambdalar dış değişkenlere başvurabilir. Bu dış değişkenler , lambda ifadesini tanımlayan yöntem kapsamında veya lambda ifadesini içeren türün kapsamında olan değişkenlerdir. Bu şekilde tutulan değişkenler, aksi halde kapsam dışına çıkacak ve çöp olarak toplanacak olsalar dahi kullanılmak üzere lambda ifadesinde saklanır. Bir lambda ifadesinde tüketilebilmesi için öncelikle mutlaka bir harici değişken tayin edilmelidir. Aşağıdaki örnek bu kuralları gösterir:

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:

  • Yakalanan bir değişken, başvuruda bulunan temsilci çöp toplama için uygun hale gelene kadar çöp toplanmaz.
  • Lambda ifadesi içinde tanıtılan değişkenler, kapsayan yöntemde görünmez.
  • Lambda ifadesi, kapsayan yöntemden bir in, ref veya out parametresini doğrudan yakalayamaz.
  • Lambda ifadesindeki return deyimi, kapsayan yöntemin döndürülmesine neden olmaz.
  • Bu atlama deyiminin hedefi lambda ifade bloğunun dışındaysa bir lambda ifadesi goto, break veya continue deyimi içeremez. Hedef, bloğun içindeyse lambda ifade bloğunun dışında bir atlama deyimi olması da hatadır.

Lambda tarafından yerel değişkenlerin static veya örnek durumunun yanlışlıkla yakalanmasını önlemek için değiştiriciyi bir lambda ifadesine uygulayabilirsiniz:

Func<double, double> square = static x => x * x;

Statik lambda, kapsamları kapsayan yerel değişkenleri veya örnek durumunu yakalayamaz, ancak statik üyelere ve sabit tanımlara başvurabilir.

C# dili belirtimi

Daha fazla bilgi için C# dil belirtiminin Anonim işlev ifadeleri bölümüne bakın.

Bu özellikler hakkında daha fazla bilgi için aşağıdaki özellik teklifi notlarını inceleyin:

Ayrıca bkz.