Lambda ifadeleri (C# Başvurusu)

Anonim bir işlev oluşturmak için bir lambda ifadesi kullanın. Lambda parametre listesini gövdesinden ayırmak için lambda bildirimi işlecini => kullanın. Bir lambda ifadesi aşağıdaki iki formdan herhangi biri olabilir:

  • Gövdesi olarak bir ifadeye sahip olan ifade lambda :

    (input-parameters) => expression
    
  • Gövdesi olarak bir ifade bloğuna sahip olan ifade lambda :

    (input-parameters) => { <sequence-of-statements> }
    

Lambda ifadesi oluşturmak için, lambda işlecinin sol tarafında (varsa) giriş parametrelerini ve diğer tarafta bir ifade ya da deyim bloğunu belirtirsiniz.

Herhangi bir lambda ifadesi, bir 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ürlerine göre tanımlanır. 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üren bir lambda ifadesi bir Action<T1,T2> temsilciye dönüştürülemez. Bir parametreye sahip olan ve bir değer döndüren bir lambda ifadesi, bir Func<T,TResult> temsilciye dönüştürülebilir. Aşağıdaki örnekte, x => x * x adlı ve kare değeri döndüren bir parametreyi belirten lambda ifadesi, x x bir temsilci türü değişkenine atanır:

Func<int, int> square = x => x * x;
Console.WriteLine(square(5));
// Output:
// 25

Aşağıdaki örnekte gösterildiği gibi, ifade lambdaları da ifade ağacı türlerine 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, örneğin, Task.Run(Action) arka planda yürütülmesi gereken kodu geçirmek için yönteme bir bağımsız değişken olarak temsilci türleri veya ifade ağaçları örnekleri gerektiren herhangi bir kodda kullanabilirsiniz. Aşağıdaki örnekte gösterildiği gibi, C# ' de LINQyazdığınızda 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

sınıfında yöntemi çağırmak için yöntem tabanlı sözdizimi kullandığınızda ( Enumerable.Select System.Linq.Enumerable örneğin, LINQ to Objects ve LINQ to XML, parametresi bir temsilci türüdür System.Func<T,TResult> . Queryable.Selectsınıfında yöntemini çağırdığınızda System.Linq.Queryable , örneğin LINQ to SQL, 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. SelectAslında Lambdalar tarafından oluşturulan nesnelerin türü farklı olsa da, bu iki çağrının benzer görünmesini sağlar.

İfade lambdaları

İşlecinin sağ tarafında bir ifade olan bir lambda ifadesine bir => ifade lambda adı verilir. 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ı kullanmamanız gerekir. Yöntemlerin .NET ortak dil çalışma zamanı (CLR) bağlamı dışında hiçbir anlamı olmayacaktır.

İfade lambdaları

Deyimler, deyimlerinin ayraç içine alınması dışında bir ifade lambda öğesine 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 deyim lambdaları kullanamazsınız.

Lambda ifadesinin giriş parametreleri

Lambda ifadesinin giriş parametrelerini parantez içine alın. 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ş parametrelerinin türlerini çıkarsamaz. Aşağıdaki örnekte gösterildiği gibi türleri açıkça belirtebilirsiniz:

Func<int, string, bool> isTooLong = (int x, string s) => s.Length > x;

Giriş parametresi türleri tamamen açık veya tümü örtük olmalıdır; Aksi halde, bir CS0748 derleyici hatası oluşur.

C# 9,0 ' den başlayarak, ifadede kullanılmayan bir lambda ifadesinin iki veya daha fazla giriş parametresini belirtmek için atarsa ' ı kullanabilirsiniz:

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

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

Not

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

Zaman uyumsuz Lambdalar

Async ve await anahtar sözcüklerini kullanarak zaman uyumsuz işleme içeren lambda ifadeleri ve deyimlerini kolayca oluşturabilirsiniz. örneğin, aşağıdaki Windows Forms örnek, zaman uyumsuz bir yöntemi çağıran ve bekleden 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 async Aşağıdaki örnekte gösterildiği gibi Lambda parametre listesinden önce bir 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öntemlerin nasıl oluşturulacağı ve kullanılacağı hakkında daha fazla bilgi için bkz. Async ve await Ile zaman uyumsuz programlama.

Lambda ifadeleri ve tanımlama grupları

C# 7,0 ile başlayarak, C# dili Tanımlama gruplarıiçin yerleşik destek sağlar. Bir 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 demet bileşenleri türlerini belirlemede tür çıkarımı kullanır.

Bir tanımlama grubu, bileşenlerinin virgülle ayrılmış bir listesini parantez içine alarak tanımlarsınız. Aşağıdaki örnek, her bir değeri iki katına çıkarır ve çarpma 'un sonucunu içeren üç bileşeni olan bir tanımlama grubu döndüren bir lambda ifadesine bir dizi sayıyı geçirmek için üç bileşeni olan tanımlama grubunu kullanı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 grubu alanları,, vb Item1 . olarak adlandırılır Item2 . 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 bilgileri hakkında daha fazla bilgi için bkz. demet türleri.

Standart sorgu işleçleri ile Lambdalar

diğer uygulamalar arasında LINQ to Objects, türü genel temsilciler ailesinden olan bir giriş parametresine sahiptir Func<TResult> . Bu temsilciler, giriş parametrelerinin sayısını ve türünü ve temsilcinin dönüş türünü tanımlamak için tür parametreleri kullanır. Func Temsilciler, bir kaynak veri kümesindeki her öğeye uygulanan Kullanıcı tanımlı ifadeleri kapsüllemek için yararlı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, Func<int, bool> int giriş parametresi olan ve dönüş değeri olan bir örnek olarak oluşturulabilir bool . Dönüş değeri her zaman son tür parametresinde belirtilir. Örneğin, Func<int, string, bool> iki giriş parametresi olan bir temsilciyi, int ve string ve dönüş türünü tanımlar bool . Aşağıdaki Func temsilci çağrıldığında, giriş parametresinin beş ' a eşit olup olmadığını belirten 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 olduğunda bir lambda ifadesi de sağlayabilirsiniz Expression<TDelegate> , örneğin, tür içinde tanımlanan standart sorgu işleçleri Queryable . Bir Expression<TDelegate> bağımsız değişken belirttiğinizde, lambda bir ifade ağacına derlenir.

Aşağıdaki örnek, Count Standart sorgu işlecini kullanı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ü çıkarabilir veya bunu açıkça belirtebilirsiniz. Bu belirli lambda ifadesi n , ikiye bölünen 1 ' in geri kalanı olduğunda bu tamsayıları () sayar.

Aşağıdaki örnek, numbers koşulu karşılamayan dizideki ilk sayı olduğundan, 9 ' dan önce gelen dizide bulunan tüm öğeleri içeren bir dizi üretir:

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, numbers değeri dizideki sıra konumundan daha az olan 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 ifadelerindekullanamazsı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 genellikle giriş parametreleri için bir tür belirtmeniz gerekmez, derleyici lambda gövdesine, parametre türlerine ve C# dil belirtiminde açıklanan diğer etkenlere göre türü çıkarsabilir. Standart sorgu işleçlerinin çoğunda ilk giriş kaynak dizisindeki öğelerin türüdür. Bir sorgulama yapıyorsanız IEnumerable<Customer> , giriş değişkeni bir nesne olarak algılanır Customer ve bu, 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 aşağıdaki gibidir:

  • 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.

Bir lambda ifadesinin doğal türü

Ortak tür sisteminin "lambda ifadesi" iç kavramı olmadığından, bir lambda ifadesinin türü yoktur. Ancak, bazen bir lambda ifadesinin "tür" i resmi olarak konuşmak yararlı olabilir. Bu resmi olmayan "tür", Expression lambda ifadesinin dönüştürüldüğü temsilci türüne veya türüne başvurur.

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

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

Derleyici parse bir olarak bulunabilir Func<string, int> . Derleyici, uygun bir tane varsa kullanılabilir Func veya Action temsilci seçer. Aksi takdirde, bir temsilci türü sentezler. Örneğin, lambda ifadesinde parametreler varsa, temsilci türü birleştirilmiştir ref . Bir lambda ifadesinin doğal bir türü olduğunda, veya gibi daha az bir açık türe atanabilir System.Object System.Delegate .

object parse = (string s) => int.Parse(s);   // Func<string, int>
Delegate parse = (string s) => int.Parse(s); // Func<string, int>

Tek bir aşırı yüklemeye sahip yöntem grupları (diğer bir deyişle, parametre listeleri olmayan yöntem adları) doğal bir türe sahiptir:

var read = Console.Read; // Just one overload; Func<int> inferred
var write = Console.Write; // ERROR: Multiple overloads, can't choose

Veya ' a bir lambda ifadesi atarsanız System.Linq.Expressions.LambdaExpression System.Linq.Expressions.Expression ve lambda doğal bir temsilci türüne sahipse, ifade tür System.Linq.Expressions.Expression<TDelegate> parametresi için bağımsız değişken olarak kullanılan doğal temsilci türü ile doğal bir türüne sahiptir:

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 bir parametre türü s çıkaramaz. Derleyici doğal bir tür çıkaramaysa, türü bildirin:

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 ertelenmiştir. Bazı ifadeler için bu işe çalışmıyor:

var choose = (bool b) => b ? 1 : "two"; // ERROR: Can't infer return type

C# 10'dan başerek, giriş parametreleri öncesinde bir lambda ifadesinin dönüş türünü belirtsiniz. Açık bir dönüş türü belirttiğinizde, giriş parametrelerini parantez içinde belirtmeniz gerekir:

var choose = object (bool b) => b ? 1 : "two"; // Func<bool, object>

Öznitelikler

C# 10'dan başarak, bir lambda ifadesine ve parametrelerine öznitelikler eklersiniz. Aşağıdaki örnek, bir lambda ifadesine öznitelik eklemeyi gösterir:

Func<string, int> parse = [Example(1)] (s) => int.Parse(s);
var choose = [Example(2)][Example(3)] object (bool b) => b ? 1 : "two";

Ayrıca, aşağıdaki örnekte de olduğu gibi giriş parametrelerine veya dönüş değerine öznitelik eklemek için:

var sum = ([Example(1)] int a, [Example(2), Example(3)] int b) => a + b;
var inc = [return: Example(1)] (int s) => s++;

Yukarıdaki örneklerde de olduğu gibi, bir lambda ifadesine veya parametrelerine öznitelik eklerken giriş parametrelerini parantez içinde ayraç olarak 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 ifadeleri üzerinde öznitelikler kod analizi için yararlıdır ve yansıma yoluyla keşfedilebilirsiniz. Bu kararın bir sonucu, bir System.Diagnostics.ConditionalAttribute lambda ifadesine uygulanamaz olmasıdır.

Lambda ifadelerinde dış değişkenleri ve değişken kapsamını yakalama

Lambda'lar dış değişkenlere başvurur. Bunlar, lambda ifadesini tanımlayan yönteminde kapsamda veya lambda ifadesini içeren türde kapsamda 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şvurulan temsilci çöp toplamaya uygun hale gelene kadar atık toplamaya uygun olmayacaktır.
  • Bir lambda ifadesinde ortaya konulan değişkenler kapsayan yöntemde görünmez.
  • Lambda ifadesi, kapsayan yöntemden doğrudan , refveya out parametresinde bir yakalamaz.
  • Lambda ifadesinde dönüş deyimi, kapsayan yöntemin dönmesine neden değildir.
  • Bu atlama deyiminin hedefi lambda ifade bloğunun dışında ise, lambda ifadesi goto, breakveya continue deyimini içerenin. Ayrıca, hedef bloğun içinde ise lambda ifade bloğunun dışında bir atlama deyimine sahip olmak da bir hatadır.

C# 9.0'dan itibaren, yerel değişkenlerin veya örnek durumunun lambda tarafından yakalanmasını engellemek için bir lambda ifadesine değiştirici static uygulayabilirsiniz:

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

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

C# dili belirtimi

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

C# 9.0 ve üzerine eklenen özellikler hakkında daha fazla bilgi için aşağıdaki özellik teklifi notlarına bakın:

Ayrıca bkz.