Výrazy lambda a anonymní funkce

K vytvoření anonymní funkce použijete výraz lambda. Pomocí operátoru => deklarace lambda oddělte seznam parametrů lambda od jejího těla. Výraz lambda může být z některé z následujících dvou forem:

  • Výraz lambda , který má výraz jako text:

    (input-parameters) => expression
    
  • Výraz lambda , který má blok příkazu jako jeho text:

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

Pokud chcete vytvořit výraz lambda, zadáte vstupní parametry (pokud existuje) na levé straně operátoru lambda a výraz nebo blok příkazu na druhé straně.

Libovolný výraz lambda lze převést na typ delegáta. Typ delegáta, na který lze převést výraz lambda, je definován typy jeho parametrů a návratovou hodnotou. Pokud výraz lambda nevrací hodnotu, dá se převést na jeden z Action typů delegátů. Jinak ho můžete převést na jeden z typů delegátů Func . Například výraz lambda, který má dva parametry a nevrací žádnou hodnotu, nelze převést na delegáta Action<T1,T2> . Výraz lambda, který má jeden parametr a vrací hodnotu, lze převést na delegáta Func<T,TResult> . V následujícím příkladu je výraz x => x * xlambda , který určuje parametr, který má název x a vrací hodnotu čtverce x , přiřazen proměnné typu delegáta:

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

Lambda výrazů lze také převést na typy stromu výrazů, jak ukazuje následující příklad:

System.Linq.Expressions.Expression<Func<int, int>> e = x => x * x;
Console.WriteLine(e);
// Output:
// x => (x * x)

Výrazy lambda můžete použít v libovolném kódu, který vyžaduje instance typů delegátů nebo stromů výrazů, například jako argument Task.Run(Action) metody pro předání kódu, který by se měl spustit na pozadí. Výrazy lambda můžete použít také při psaní LINQ v jazyce C#, jak ukazuje následující příklad:

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

Pokud používáte syntaxi založenou Enumerable.Select na metodách k volání metody ve System.Linq.Enumerable třídě, například v LINQ to Objects a LINQ to XML, je parametr typu System.Func<T,TResult>delegáta . Při volání Queryable.Select metody ve System.Linq.Queryable třídě, například v LINQ to SQL, typ parametru je typ Expression<Func<TSource,TResult>>stromu výrazu . V obou případech můžete k zadání hodnoty parametru použít stejný výraz lambda. Díky tomu budou dvě Select volání vypadat podobně, i když ve skutečnosti se typ objektů vytvořených z lambda liší.

Výrazy lambda

Výraz lambda s výrazem na pravé straně operátoru => se nazývá lambda výrazu. Výrazová lambda vrátí výsledek výrazu a má následující základní podobu:

(input-parameters) => expression

Tělo výrazu lambda se může skládat z volání metody. Pokud ale vytváříte stromy výrazů , které se vyhodnocují mimo kontext modulu CLR (Common Language Runtime), například v SQL Serveru, neměli byste ve výrazech lambda používat volání metod. Metody nemají žádný význam mimo kontext modulu CLR (Common Language Runtime).

Výrazy lambda

Výraz lambda se podobá výrazu lambda s tím rozdílem, že jeho příkazy jsou uzavřeny ve složených závorkách:

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

Text příkazové lambdy může obsahovat libovolný počet příkazů. V praxi jich však není obvykle více než dva nebo tři.

Action<string> greet = name =>
{
    string greeting = $"Hello {name}!";
    Console.WriteLine(greeting);
};
greet("World");
// Output:
// Hello World!

Výrazy lambda nemůžete použít k vytvoření stromů výrazů.

Vstupní parametry výrazu lambda

Vstupní parametry výrazu lambda uzavřete do závorek. Zadejte nulové vstupní parametry s prázdnými závorkami:

Action line = () => Console.WriteLine();

Pokud výraz lambda obsahuje pouze jeden vstupní parametr, jsou závorky volitelné:

Func<double, double> cube = x => x * x * x;

Dva nebo více vstupních parametrů jsou oddělené čárkami:

Func<int, int, bool> testForEquality = (x, y) => x == y;

Kompilátor někdy nemůže odvodit typy vstupních parametrů. Typy můžete zadat explicitně, jak je znázorněno v následujícím příkladu:

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

Vstupní typy parametrů musí být explicitní nebo všechny implicitní; jinak dojde k chybě kompilátoru CS0748 .

Pomocí zahození můžete zadat dva nebo více vstupních parametrů výrazu lambda, který se ve výrazu nepoužívá:

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

Parametry zahození lambda můžou být užitečné při použití výrazu lambda k poskytnutí obslužné rutiny události.

Poznámka:

Pro zpětnou kompatibilitu, pokud je pojmenován _pouze jeden vstupní parametr , pak v rámci výrazu lambda je _ považován za název tohoto parametru.

Počínaje jazykem C# 12 můžete zadat výchozí hodnoty parametrů pro výrazy lambda. Syntaxe a omezení výchozích hodnot parametrů jsou stejné jako u metod a místních funkcí. Následující příklad deklaruje výraz lambda s výchozím parametrem a potom ho volá jednou pomocí výchozího a jednou se dvěma explicitními parametry:

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

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

Výrazy lambda můžete deklarovat také pomocí params polí jako parametrů:

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

V rámci těchto aktualizací má skupina metod, která má výchozí parametr přiřazený výrazu lambda, má tento výraz lambda také stejný výchozí parametr. Skupinu metod s parametrem params pole lze také přiřadit k výrazu lambda.

Výrazy lambda s výchozími parametry nebo params poli jako parametry nemají přirozené typy, které odpovídají nebo Action<> typůmFunc<>. Můžete ale definovat typy delegátů, které obsahují výchozí hodnoty parametrů:

delegate int IncrementByDelegate(int source, int increment = 1);
delegate int SumDelegate(params int[] values);

Nebo můžete k definování typu delegáta použít implicitně zadané proměnné s var deklaracemi. Kompilátor syntetizuje správný typ delegáta.

Další informace o specifikaci funkce pro výchozí parametry výrazů lambda.

Asynchronní lambda

Výrazy a příkazy lambda, které zahrnují asynchronní zpracování, můžete snadno vytvořit pomocí klíčových slov async a await . Například následující model Windows Forms příklad obsahuje obslužnou rutinu události, která volá a čeká asynchronní metodu, 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);
    }
}

Stejný ovladač událostí můžete přidat pomocí asynchronní lambdy. Pokud chcete přidat tuto obslužnou rutinu async , přidejte před seznam parametrů lambda modifikátor, jak ukazuje následující příklad:

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

Další informace o vytváření a používání asynchronních metod naleznete v tématu Asynchronní programování pomocí async a await.

Výrazy lambda a řazené kolekce členů

Jazyk C# poskytuje integrovanou podporu pro řazené kolekce členů. Jako argument výrazu lambda můžete zadat řazenou kolekci členů a výraz lambda může také vrátit řazenou kolekci členů. V některých případech kompilátor jazyka C# používá odvození typu k určení typů součástí řazené kolekce členů.

Řazenou kolekci členů definujete uzavřením čárkami odděleného seznamu jejích součástí do závorek. Následující příklad používá řazenou kolekci členů se třemi komponentami k předání posloupnosti čísel výrazu lambda, který zdvojnásobí každou hodnotu a vrátí řazenou kolekci členů se třemi komponentami, které obsahují výsledek násobení.

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)

Obvykle jsou pole řazené kolekce členů pojmenována Item1atd Item2. Můžete však definovat řazenou kolekci členů s pojmenovanými komponentami, jak to dělá následující příklad.

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

Další informace o řazených kolekcí členů jazyka C# naleznete v tématu Typy řazené kolekce členů.

Lambdas se standardními operátory dotazů

LINQ to Objects má mimo jiné vstupní parametr, jehož typ je jednou z Func<TResult> řad obecných delegátů. Tito delegáti používají parametry typu k definování počtu a typu vstupních parametrů a návratového typu delegáta. Func delegáty jsou užitečné pro zapouzdření uživatelem definovaných výrazů, které jsou použity pro každý prvek v sadě zdrojových dat. Představte si například typ delegáta Func<T,TResult> :

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

Delegát může být vytvořena jako Func<int, bool> instance, kde int je vstupní parametr a bool je návratovou hodnotou. Vrácená hodnota je vždy určena v posledním parametru typu. Například Func<int, string, bool> definuje delegáta se dvěma vstupními parametry int a stringa návratovým typem bool. Následující Func delegát při vyvolání vrátí logickou hodnotu, která označuje, jestli je vstupní parametr roven pěti:

Func<int, bool> equalsFive = x => x == 5;
bool result = equalsFive(4);
Console.WriteLine(result);   // False

Výraz lambda můžete také zadat, pokud je typ argumentu Expression<TDelegate>, například ve standardních operátorech dotazu, které jsou definovány v Queryable typu. Když zadáte Expression<TDelegate> argument, lambda se zkompiluje do stromu výrazů.

Následující příklad používá Count standardní operátor dotazu:

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

Kompilátor může odvodit typ vstupního parametru, nebo jej můžete nastavit také explicitně. Tento konkrétní výraz lambda spočítá celá čísla (n), která při dělení dvěma mají zbytek 1.

Následující příklad vytvoří sekvenci, která obsahuje všechny prvky v numbers matici, která předchází 9, protože to je první číslo v posloupnosti, která nesplňuje podmínku:

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

Následující příklad určuje více vstupních parametrů uzavřením do závorek. Metoda vrátí všechny prvky v numbers matici, dokud nenajde číslo, jehož hodnota je menší než jeho pořadová pozice v matici:

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

Výrazy lambda nepoužíváte přímo ve výrazech dotazu, ale můžete je použít ve volání metod v rámci výrazů dotazu, jak ukazuje následující příklad:

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

Odvození typu ve výrazech lambda

Při psaní lambda často nemusíte zadávat typ vstupních parametrů, protože kompilátor může typ odvodit na základě těla lambda, typů parametrů a dalších faktorů popsaných ve specifikaci jazyka C#. Pro většinu standardních operátorů pro dotazování je prvním vstupem typ prvků ve zdrojové sekvenci. Pokud dotazujete IEnumerable<Customer>objekt, je vstupní proměnná odvozena jako Customer objekt, což znamená, že máte přístup k jeho metodám a vlastnostem:

customers.Where(c => c.City == "London");

Obecná pravidla pro odvození typu pro lambda jsou následující:

  • Výraz lambda musí obsahovat stejný počet parametrů jako typ delegátu.
  • Každý vstupní parametr ve výrazu lambda musí být implicitně převoditelný na odpovídající parametr delegátu.
  • Vrácená hodnota lambda (pokud existuje) musí být implicitně převoditelná na návratový typ delegátu.

Přirozený typ výrazu lambda

Výraz lambda sám o sobě nemá typ, protože systém běžných typů nemá žádný vnitřní koncept výrazu lambda. Někdy je ale vhodné mluvit neformálně o "typu" výrazu lambda. Tento neformální "typ" odkazuje na typ delegáta nebo Expression typ, na který je výraz lambda převeden.

Počínaje jazykem C# 10 může mít výraz lambda přirozený typ. Místo vynucení deklarování typu delegáta, například Func<...>Action<...> výrazu lambda, může kompilátor odvodit typ delegáta z výrazu lambda. Předpokládejme například následující deklaraci:

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

Kompilátor může odvoditparse, že je to .Func<string, int> Kompilátor zvolí dostupný Func nebo Action delegát, pokud existuje vhodný. V opačném případě syntetizuje typ delegáta. Například typ delegáta je syntetizován, pokud výraz lambda má ref parametry. Pokud má výraz lambda přirozený typ, může být přiřazen méně explicitnímu typu, například System.ObjectSystem.Delegate:

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

Skupiny metod (to znamená názvy metod bez seznamů parametrů) s přesně jedním přetížením mají přirozený typ:

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

Pokud přiřadíte výraz System.Linq.Expressions.LambdaExpressionlambda nebo System.Linq.Expressions.Expressiona lambda má přirozený typ delegáta, výraz má přirozený typ , s přirozeným typem System.Linq.Expressions.Expression<TDelegate>delegáta použitým jako argument parametru typu:

LambdaExpression parseExpr = (string s) => int.Parse(s); // Expression<Func<string, int>>
Expression parseExpr = (string s) => int.Parse(s);       // Expression<Func<string, int>>

Ne všechny výrazy lambda mají přirozený typ. Představte si následující deklaraci:

var parse = s => int.Parse(s); // ERROR: Not enough type info in the lambda

Kompilátor nemůže odvodit typ parametru pro s. Pokud kompilátor nemůže odvodit přirozený typ, musíte deklarovat typ:

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

Explicitní návratový typ

Návratový typ výrazu lambda je obvykle zřejmý a odvozený. U některých výrazů, které nefungují:

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

Počínaje jazykem C# 10 můžete před vstupními parametry zadat návratový typ výrazu lambda. Pokud zadáte explicitní návratový typ, je nutné závorky zadat vstupní parametry:

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

Atributy

Počínaje jazykem C# 10 můžete přidat atributy do výrazu lambda a jeho parametrů. Následující příklad ukazuje, jak přidat atributy do výrazu lambda:

Func<string?, int?> parse = [ProvidesNullCheck] (s) => (s is not null) ? int.Parse(s) : null;

Můžete také přidat atributy ke vstupním parametrům nebo návratové hodnotě, jak ukazuje následující příklad:

var concat = ([DisallowNull] string a, [DisallowNull] string b) => a + b;
var inc = [return: NotNullIfNotNull(nameof(s))] (int? s) => s.HasValue ? s++ : null;

Jak ukazuje předchozí příklady, je nutné závorky vstupní parametry přidat do výrazu lambda nebo jeho parametrů.

Důležité

Výrazy lambda se vyvolávají prostřednictvím základního typu delegáta. To se liší od metod a místních funkcí. Metoda delegáta Invoke nekontroluje atributy výrazu lambda. Atributy nemají žádný vliv při vyvolání výrazu lambda. Atributy výrazů lambda jsou užitečné pro analýzu kódu a lze je zjistit prostřednictvím reflexe. Jedním z důsledků tohoto rozhodnutí je, že System.Diagnostics.ConditionalAttribute výraz lambda nelze použít.

Zachycení vnějších proměnných a rozsahu proměnných ve výrazech lambda

Lambda můžou odkazovat na vnější proměnné. Tyto vnější proměnné jsou proměnné, které jsou v oboru v metodě, která definuje výraz lambda, nebo v oboru v typu, který obsahuje výraz lambda. Proměnné, které jsou zachyceny tímto způsobem, jsou uloženy pro použití ve výrazu lambda i v případě, že proměnné by jinak přesáhly rozsah platnosti a bylo by vynuceno uvolnění paměti. Vnější proměnná musí být jednoznačně přiřazena dříve, než může být upotřebena ve výrazu lambda. Následující příklad znázorňuje tato pravidla:

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
}

Následující pravidla se vztahují na rozsah proměnné ve výrazu lambda:

  • Proměnná, která je zachycena, se neshromažďuje, dokud delegát, který na něj odkazuje, nebude způsobilý pro uvolňování paměti.
  • Proměnné zavedené ve výrazu lambda nejsou viditelné v ohraničující metodě.
  • Výraz lambda nemůže přímo zachytit parametr in, ref nebo out z uzavřené metody.
  • Návratový příkaz ve výrazu lambda nezpůsobí vrácení ohraničující metody.
  • Výraz lambda nemůže obsahovat příkaz goto, break nebo continue , pokud cíl tohoto příkazu skoku je mimo blok výrazu lambda. Chyba také obsahuje příkaz jump mimo blok výrazu lambda, pokud je cíl uvnitř bloku.

Modifikátor můžete použít static u výrazu lambda, abyste zabránili neúmyslnému zachycení místních proměnných nebo stavu instance lambda:

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

Statická lambda nemůže zachytit místní proměnné nebo stav instance z uzavřených oborů, ale může odkazovat na statické členy a definice konstant.

specifikace jazyka C#

Další informace naleznete v části Anonymní výrazy funkce specifikace jazyka C#.

Další informace o těchto funkcích najdete v následujících poznámkách k návrhu funkcí:

Viz také