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.

Lambda ifadeleri için doğal tür

Ortak tür sisteminde "lambda ifadesi" iç kavramı bulunmadığından, kendi içindeki lambda ifadeleri bir türe sahip değildir. 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 bazı lambda ifadeleri doğal bir türe sahiptir. Bir lambda ifadesi gibi bir temsilci türü bildirmeye zorlamak yerine, Func<...> derleyici, Action<...> parametrelerden ve ifadenin türünden 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> . Genel olarak, derleyici uygun bir tane varsa kullanılabilir Func veya Action temsilciyi kullanır. Aksi takdirde, bir temsilci türünü sentezleştir. Örneğin, lambda ifadesinde parametreler varsa tür sentezleştirilmiş olmalıdır 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>

Tam olarak bir aşırı yüklemesi olan yöntem grupları (yani, bağımsız değişken listesi olmayan yöntem adları) doğal bir tür olur:

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

Birçok lambda ifadesi doğal bir türe sahip olmayacaktır. 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ü çıkarsanamıyor s . Derleyici doğal bir tür çıkarsanamıyor, türü bildirmeniz gerekir:

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

Belirtilen dönüş türü

Genellikle, lambda ifadesinin dönüş türü açıktır ve algılanır. Bazı ifadelerde çalışmayabilir:

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

C# 10 ' dan başlayarak, parametrelerden önce bir lambda ifadesinde dönüş türünü belirtebilirsiniz. Açık bir dönüş türü belirttiğinizde, parametreler parantez içine alınmalıdır, bu nedenle derleyici veya diğer geliştiricilere kafa karıştırıcı olmaz:

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

Öznitelikler

C# 10 ' dan başlayarak lambda ifadelerine öznitelikler uygulayabilirsiniz. Öznitelikler parametre bildiriminden önce eklenir. Öznitelikler varsa lambda ifadesinin parametre listesinin parantez olması gerekir:

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

Üzerinde geçerli olan herhangi bir özniteliği uygulayabilirsiniz AttributeTargets.Method .

Lambda ifadeleri, temel alınan temsilci türü aracılığıyla çağrılır. Yöntemler ve yerel işlevlerden farklıdır. Temsilcinin Invoke yöntemi lambda ifadesindeki öznitelikleri denetlemez. Lambda ifadesi çağrıldığında özniteliklerin hiçbir etkisi yoktur. Lambda ifadelerindeki öznitelikler kod analizi için faydalıdır ve yansıma aracılığıyla bulunabilir. Bu kararın bir sonucu System.Diagnostics.ConditionalAttribute bir lambda ifadesine uygulanamaz.

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

Lambdalar, dış değişkenlere başvurabilir. Bunlar, lambda ifadesini tanımlayan yöntemde veya lambda ifadesini içeren türde kapsamda kapsam içinde 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 çöp toplama için uygun hale gelene kadar çöp toplanmaz.
  • Bir lambda ifadesi içinde tanıtılan değişkenler kapsayan yöntemde görünmez.
  • Lambda ifadesi kapsayan yöntemden bir ın, refveya Out parametresini doğrudan yakalayamaz.
  • Lambda ifadesindeki bir dönüş deyimi kapsayan metodun dönüşmesine neden olmaz.
  • Bir lambda ifadesi, bu sıçrama deyiminin hedefi lambda ifade bloğunun dışındaysa bir goto, Breakveya Continue deyimi içeremez. Ayrıca, hedef bloğun içindeyse lambda ifade bloğunun dışında bir sıçrama deyimine sahip olmak için bir hatadır.

C# 9,0 ' den başlayarak static lambda ile yerel değişkenlerin veya örnek durumunun istenmeden yakalanmasını engellemek için bir lambda ifadesine değiştiricisini uygulayabilirsiniz:

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

Statik lambda, kapsayan kapsamların yerel değişkenlerini veya örnek durumunu yakalayabilir, 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.

C# 9,0 ' de eklenen özellikler hakkında daha fazla bilgi için aşağıdaki özellik teklifi notlarına bakın:

Ayrıca bkz.