Lambda – výrazy (Referenční dokumentace 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 některou z následujících dvou forem:

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

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

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

Chcete-li vytvořit výraz lambda, zadejte vstupní parametry (pokud existují) 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 výraz lambda převést, je definován typy jeho parametrů a návratové hodnoty. Pokud výraz lambda nevrací hodnotu, lze jej převést na jeden z Action typů delegátů. v opačném případě jej lze převést na jeden z Func typů delegátů. Například lambda výraz, který má dva parametry a nevrací žádnou hodnotu, lze převést na Action<T1,T2> delegáta. Lambda výraz, který má jeden parametr a vrací hodnotu, lze převést na Func<T,TResult> delegáta. V následujícím příkladu lambda výraz x => x * x , který určuje parametr s názvem x a vrací hodnotu x Square, je přiřazen proměnné typu delegáta:

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

Lambda výrazy 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)

Můžete použít výrazy lambda v jakémkoli kódu, který vyžaduje instance typů delegátů nebo stromů výrazů, například jako argument Task.Run(Action) metody k předání kódu, který by měl být proveden na pozadí. Lambda výrazy 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

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

Výrazy lambda výrazu

Výraz lambda s výrazem na pravé straně => operátoru se nazývá výrazová 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é jsou vyhodnocovány mimo kontext modulu clr (Common Language Runtime) .net, například v SQL Server, neměli byste používat volání metody ve výrazech lambda. Metody nebudou mít žádný význam mimo kontext modulu CLR (Common Language Runtime) .NET.

Výrazy lambda příkazů

Výraz lambda se podobá lambda výrazu 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 příkazů nelze použít k vytváření stromů výrazů.

Vstupní parametry výrazu lambda

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

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

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

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

Dva nebo více vstupních parametrů je odděleno čárkami:

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

Kompilátor někdy nemůže odvodit typy vstupních parametrů. Můžete určit typy 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;

Typy vstupních parametrů musí být všechny explicitní nebo všechny implicitní; v opačném případě dojde k chybě kompilátoru CS0748 .

Počínaje jazykem C# 9,0 můžete použít možnost Discard k zadání dvou nebo více vstupních parametrů výrazu lambda, který není použit ve výrazu:

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

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

Poznámka

V případě zpětné kompatibility, je-li pouze jeden vstupní parametr nazvaný _ , je v rámci výrazu lambda _ považována za název tohoto parametru.

Asynchronní výrazy lambda

Můžete snadno vytvořit výrazy lambda a příkazy, které zahrnují asynchronní zpracování pomocí klíčových slov Async a await . například následující příklad model Windows Forms obsahuje obslužnou rutinu události, která volá a očekává 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. Chcete-li přidat tuto obslužnou rutinu, přidejte async Modifikátor před seznam parametrů 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 tom, jak vytvořit a používat asynchronní metody, naleznete v tématu asynchronní programování s modifikátorem Async a await.

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

Počínaje jazykem C# 7,0 poskytuje jazyk C# integrovanou podporu pro řazené kolekce členů. Můžete zadat řazenou kolekci členů jako argument pro lambda výraz a váš výraz lambda může také vrátit řazenou kolekci členů. V některých případech kompilátor C# používá odvození typu k určení typů komponent řazené kolekce členů.

Řazenou kolekci členů můžete definovat tak, že v závorkách seřadíte seznam jeho komponent oddělených čárkami. Následující příklad používá řazené kolekce členů se třemi komponenty k předání sekvence čísel lambda výrazu, který zdvojnásobuje 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)

Pole řazené kolekce členů jsou obvykle pojmenovány Item1 , Item2 a tak dále. Můžete však definovat řazenou kolekci členů s pojmenovanými součástmi, jak je uvedeno v následujícím příkladu.

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ích členů jazyka C# naleznete v tématu typy řazené kolekce členů.

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

LINQ to Objects, mimo jiné implementace, má vstupní parametr, jehož typ je jeden z řad Func<TResult> generických delegátů. Tito Delegáti používají parametry typu pro definování počtu a typu vstupních parametrů a návratový typ delegáta. Func Delegáti jsou užiteční pro zapouzdření uživatelsky definovaných výrazů, které jsou aplikovány na každý prvek v sadě zdrojových dat. Zvažte například Func<T,TResult> typ delegáta:

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

Delegát může být vytvořen 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, a int string a návratový typ bool . Následující Func delegát, pokud je vyvolána, vrátí logickou hodnotu, která označuje, zda je vstupní parametr roven pěti:

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

Výraz lambda lze také zadat, pokud je typ argumentu, například Expression<TDelegate> ve standardních operátorech dotazu, které jsou definovány v Queryable typu. Při zadání Expression<TDelegate> argumentu je výraz lambda zkompilován do stromu výrazu.

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 počítá tato celá čísla ( n ), která při vydělení dvěma mají zbytek 1.

Následující příklad vytvoří sekvenci, která obsahuje všechny prvky v numbers poli, které předcházejí hodnotě 9, protože se jedná o první číslo v sekvenci, 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 poli, dokud nenajde číslo, jehož hodnota je menší než pořadové místo 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žíváte přímo ve výrazech dotazů, ale můžete je použít v volání metody ve výrazech dotazů, 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 není často nutné zadat typ pro vstupní parametry, protože kompilátor může odvodit typ na základě těla 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 IEnumerable<Customer> , pak vstupní proměnná je odvozena jako Customer objekt, což znamená, že máte přístup ke svým metodám a vlastnostem:

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

Obecná pravidla pro odvození typu 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.

Přirozený typ pro výrazy lambda

Lambda výrazy samy nemají typ, protože společný typ systému nemá žádný vnitřní koncept výrazu lambda. Někdy je však vhodné mluvit neformálně jako "typ" výrazu lambda. Tento neformální "typ" odkazuje na typ delegáta nebo Expression typ, na který je převeden výraz lambda.

Počínaje jazykem C# 10 některé lambda výrazy mají přirozený typ. Namísto vynucení deklarace typu delegáta, jako například Func<...> nebo Action<...> pro lambda výraz, kompilátor může odvodit typ delegáta z parametrů a typu výrazu. Předpokládejme například následující deklaraci:

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

Kompilátor může být odvozený parse jako Func<string, int> . Obecně platí, že kompilátor použije k dispozici Func nebo Action delegáta, pokud existuje vhodný. V opačném případě bude vysyntetizovat typ delegáta. Například typ musí být syntetizovaná, pokud má výraz lambda ref parametry. Pokud má výraz lambda přirozený typ, lze jej přiřadit k méně explicitnímu typu, například System.Object nebo System.Delegate :

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

Skupiny metod (tj. názvy metod bez seznamů argumentů) s právě 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 lambda k System.Linq.Expressions.LambdaExpression , nebo System.Linq.Expressions.Expression a lambda má typ přirozeného delegáta, má výraz přirozený typ System.Linq.Expressions.Expression<TDelegate> , s typem přirozeného delegáta použitým jako argument pro parametr typu:

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

Mnoho výrazů lambda nebude mít přirozený typ. Zvažte 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);

Deklarovaný návratový typ

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

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

Počínaje jazykem C# 10 můžete zadat návratový typ výrazu lambda před parametry. Když zadáte explicitní návratový typ, parametry musí být v závorkách, aby to nebylo pro kompilátor nebo jiné vývojáře příliš matoucí:

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

Atributy

Počínaje jazykem C# 10 můžete použít atributy pro výrazy lambda. Atributy jsou přidány před deklaraci parametru. Seznam parametrů výrazu lambda musí být v závorkách, pokud existují atributy:

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

Můžete použít libovolný atribut, který je platný pro AttributeTargets.Method .

Výrazy lambda jsou vyvolány prostřednictvím základního typu delegátu. To se liší od metod a místních funkcí. Metoda delegáta Invoke nebude kontrolovat atributy výrazu lambda. Atributy nemají při vyvolání výrazu lambda žádný vliv. 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í System.Diagnostics.ConditionalAttribute je, že nelze použít na výraz lambda.

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:

  • Zachycená proměnná nebude uvolněna z paměti, dokud delegát, který na něj odkazuje, nebude způsobilý pro 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é