Výrazy lambda (referenční příručka jazyka C#)

Pomocí výrazu lambda vytvoříte anonymní funkci. Pomocí operátoru deklarace => lambda oddělte seznam parametrů lambda od jeho těla. Výraz lambda může mít libovolnou z následujících dvou forem:

  • Výrazová lambda, která má jako tělo výraz:

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

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

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

Libovolný výraz lambda lze převést na typ delegátu. Typ delegáta, na který lze výraz lambda převést, je definován typy jeho parametrů a návratovou hodnotou. Pokud výraz lambda nevrací hodnotu, lze ji převést na jeden z typů delegátu. V opačném případě může být převeden na jeden Action Func z typů delegátu. Například výraz lambda, který má dva parametry a vrací žádnou hodnotu, lze převést na Action<T1,T2> delegáta. Výraz lambda, který má jeden parametr a vrací hodnotu, lze převést na Func<T,TResult> delegáta. V následujícím příkladu je výraz lambda , který určuje parametr s názvem a vrací hodnotu squared, přiřazen k proměnné typu x => x * x x x delegátu:

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

Výrazové výrazy lambda 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 metody pro předání kódu, který se má provést na Task.Run(Action) pozadí. Výrazy lambda můžete použít také při zápisu 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žijete syntaxi založenou na metodě k volání metody ve třídě , například v LINQ to Objects a LINQ to XML, je parametr Enumerable.Select System.Linq.Enumerable typu delegátu System.Func<T,TResult> . Když zavoláte metodu ve třídě , například v LINQ to SQL, typ parametru Queryable.Select System.Linq.Queryable je typ stromu výrazu Expression<Func<TSource,TResult>> . V obou případech můžete k zadání hodnoty parametru použít stejný výraz lambda. Díky tomu obě volání vypadají podobně, i když ve skutečnosti se typ objektů vytvořených z Select výrazů lambda liší.

Výrazové výrazy lambda

Výraz lambda s výrazem na pravé straně operátoru se nazývá => výraz lambda. 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 však vytváříte stromy výrazů, které se vyhodnocují mimo kontext modulu CLR (Common Language Runtime) technologie .NET, například v modulu SQL Server, neměli byste ve výrazech lambda používat volání metod. Metody nebudou mít žádný význam mimo kontext modulu .NET Common Language Runtime (CLR).

Výrazové výrazy lambda

Výrazová lambda se podobá výrazové lambdě 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ýrazové výrazy lambda nelze 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, závorky jsou 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.

Počínaje jazykem C# 9.0 můžete pomocí metody discard zadat dva nebo více vstupních parametrů výrazu lambda, které se ve výrazu nepoužily:

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

Lambda discard parameters may be useful when you use a lambda expression to provide an event handler.

Poznámka

Pokud má kvůli zpětné kompatibilitě název pouze jeden vstupní parametr , pak se ve výrazu lambda zachází jako s _ _ názvem tohoto parametru.

Asynchronní výrazy lambda

Pomocí klíčových slov async a await můžete snadno vytvářet výrazy lambda a příkazy, které zahrnují asynchronní zpracování. Například následující příklad Windows Forms obsahuje obslužnou rutinu události, která volá asynchronní metodu a čeká na ExampleMethodAsync ji.

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, přidejte modifikátor před seznam parametrů async lambda, 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 najdete v tématu Asynchronní programování pomocí modifikátoru Asynca operátoru Await.

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

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

Řazenou kolekce členů definujete uzavřením seznamu jejích komponent oddělených čárkami do závorek. Následující příklad používá řazenou kolekce členů se třemi komponentami k předání posloupnosti čísel výrazu lambda, který zdvojnásobí každou hodnotu a vrátí řazenou kolekce č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 mají pole řazené kolekce členů název Item1 Item2 , atd. Můžete však definovat řazenou kolekce členů s pojmenovaných komponent, 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 kolekce členů jazyka C# najdete v tématu Typy řazené kolekce členů.

Výrazy lambda se standardními operátory dotazu

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

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

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

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

Výraz lambda můžete zadat také v případě, že typ argumentu je , například ve standardních operátorech dotazu, které jsou Expression<TDelegate> 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á standardní Count 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 ( ), která při vydělování dvěma n mají zbytek 1.

Následující příklad vytvoří sekvenci, která obsahuje všechny prvky v poli, které předcházejí 9, protože toto je první číslo v sekvenci, které nesplňuje numbers 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ů jejich uzavřením do závorek. Metoda vrátí všechny prvky v poli, dokud nenaleznou číslo, jehož hodnota je menší než jeho pořadová numbers pozice v poli:

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žívejte přímo ve výrazech dotazů,ale můžete je použít ve voláních metod ve výrazech 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í výrazů lambda často není nutné určit typ pro vstupní parametry, protože kompilátor může odvodit typ na základě textu lambda, typů parametrů a dalších faktorů, jak je popsáno 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 se dotazuje na , znamená to, že vstupní proměnná je objekt, což znamená, že máte přístup k IEnumerable<Customer> jeho metodám a Customer vlastnostem:

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

Obecná pravidla pro odvozování typů pro výrazy 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.

Všimněte si, že výrazy lambda samy o sobě nemají typ, protože běžný systém typů nemá žádný vnitřní koncept výrazu lambda. Někdy je ale vhodné neformálně mluvit o "typu" výrazu lambda. V těchto případech typ odkazuje na typ delegátu nebo Expression typ, na který je výraz lambda převeden.

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

Výrazy lambda mohou odkazovat na vnější proměnné. Jedná se o 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, nebude uvolněna z paměti, dokud delegát, který na ni odkazuje, nebude mít oprávnění k uvolňování paměti.

  • Proměnné zavedené v rámci výrazu lambda nejsou viditelné v nadřazené metodě.

  • Výraz lambda nemůže přímo zachytit parametr v, refnebo out z nadřazené metody.

  • Příkaz return ve výrazu lambda nezpůsobí vrácení nadřazené metody.

  • Výraz lambda nemůže obsahovat příkaz goto, breaknebo continue, pokud je cíl tohoto příkazu jump mimo blok výrazu lambda. Je také chybou mít příkaz jump mimo blok výrazu lambda, pokud je cíl uvnitř bloku .

Počínaje jazykem C# 9.0 můžete použít modifikátor na výraz lambda, aby se zabránilo neúmyslnému zachycení místních proměnných nebo stavu instance static výrazem lambda:

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

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

specifikace jazyka C#

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

Další informace o funkcích přidaných v jazyce C# 9.0 najdete v následujících poznámkách k návrhu funkcí:

Viz také