Výrazy Lambda (Průvodce programováním v C#)

Výraz lambda je anonymní funkce, kterou můžete použít k vytvoření typů delegátů nebo stromu výrazu.Pomocí výrazů lambda můžete psát místní funkce, které mohou být předány jako argumenty nebo vráceny jako hodnota volání funkce.Výrazy lambda jsou zvláště užitečné pro psaní výrazů dotazů LINQ.

Chcete-li vytvořit výraz lambda, zadejte vstupní parametry (pokud existují) na levé straně operátoru lambda => a umístěte blok výrazu nebo příkazu na druhou stranu.Například výraz lambda x => x * x určuje parametr s názvem x a vrátí hodnotu x ve čtverci.Tento výraz můžete přiřadit typu delegátu, jak ukazuje následující příklad:

delegate int del(int i);
static void Main(string[] args)
{
    del myDelegate = x => x * x;
    int j = myDelegate(5); //j = 25
}

Postup vytvoření typu stromu výrazu:

using System.Linq.Expressions;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Expression<del> myET = x => x * x;
        }
    }
}

Operátor => má stejnou prioritu jako přiřazení (=) a je asociativní zprava (informace najdete v části „Asociativita“ v článku věnovaném operátorům).

Výrazy lambda se používají v dotazech LINQ založených na metodách jako standardní metody operátoru dotazu, jako je Where``1.

Pokud použijete pro volání metody Where``1 ve třídě Enumerable syntaxi založenou na metodě (a to stejným způsobem jako v části LINQ pro objekty a v části Technologie LINQ to XML), je parametrem typ delegátu Func.Výraz lambda je nejvhodnějším způsobem vytvoření tohoto delegátu.Při volání stejné metody například ve třídě Queryable (stejně jako v případě Technologie LINQ to SQL) je typ parametru Expression<Func>, kde výraz Func je libovolný delegát až se šestnácti vstupními parametry.Výraz lambda je opět pouze velmi stručným způsobem vytvoření tohoto stromu výrazu.Výrazy lambda umožňují, aby volání Where bylo obdobné, ačkoli je ve skutečnosti typ objektu vytvořený z výrazu lambda jiný.

V předchozím příkladu si povšimněte, že podpis delegátu má jeden implicitně zadaný vstupní parametr typu int a vrátí hodnotu int.Výraz lambda může být převeden na delegát daného typu, protože má také jeden vstupní parametr (x) a návratovou hodnotu, kterou může kompilátor implicitně převést na typ int. (Odvození typu je popsáno podrobněji v následujících částech). Pokud je delegát vyvolán pomocí vstupního parametru 5, vrátí výsledek 25.

Výrazy lambda nejsou povoleny na levé straně operátoru is nebo as.

Všechna omezení, která platí pro anonymní metody, platí také pro výrazy lambda.Další informace naleznete v tématu Anonymní metody (Průvodce programováním v C#).

Výrazové lambdy

Výraz lambda s výrazem na pravé straně operátoru => se nazývá výrazová lambda.Výrazové lambdy se často používají v konstrukci Stromy výrazů (C# a Visual Basic).Výrazová lambda vrátí výsledek výrazu a má následující základní podobu:

(input parameters) => expression

Závorky jsou nepovinné pouze v případě, že lambda má jeden vstupní parametr; v opačném případě jsou vyžadovány.Dva nebo více vstupních parametrů je odděleno čárkami, které jsou uvedeny v závorkách:

(x, y) => x == y

Někdy je pro kompilátor obtížné či nemožné odvodit vstupní typy.V takovém případě můžete určit typy explicitně, jak je znázorněno v následujícím příkladu:

(int x, string s) => s.Length > x

Zadejte nulové vstupní parametry s prázdnými závorkami:

() => SomeMethod()

V předchozím příkladu si všimněte, že hlavní část výrazové lambdy může být tvořena voláním metody.Pokud však vytváříte stromy výrazu, které jsou vyhodnocovány mimo rozhraní .NET Framework, například na SQL Server, neměli byste používat volání metody ve výrazech lambda.Metody nemají význam mimo kontext modulu CLR (Common Language Runtime) rozhraní .NET.

Příkazové lambdy

Příkazová lambda se podobá výrazové lambdě s tím rozdílem, že výrazy jsou uzavřeny ve složených závorkách:

(input parameters) => {statement;}

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.

delegate void TestDelegate(string s);
…
TestDelegate myDel = n => { string s = n + " " + "World"; Console.WriteLine(s); };
myDel("Hello");

Příkazové lambdy nelze stejně jako anonymní metody používat k vytvoření stromů výrazu.

Asynchronní lambdy

Můžete snadno vytvořit výrazy a příkazy lambda, které zahrnují asynchronní zpracování pomocí klíčových slov async a await.Následující příklad formulářů Windows Forms obsahuje ovladač událostí, který volá a očekává asynchronní metodu ExampleMethodAsync.

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private async void button1_Click(object sender, EventArgs e)
    {
        // ExampleMethodAsync returns a Task.
        await ExampleMethodAsync();
        textBox1.Text += "\r\nControl returned to Click event handler.\r\n";
    }

    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 tento ovladač, přidejte modifikátor async před seznam parametrů lambda, jak je uvedeno v následujícím příkladu.

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        button1.Click += async (sender, e) =>
        {
            // ExampleMethodAsync returns a Task.
            await ExampleMethodAsync();
            textBox1.Text += "\r\nControl returned to Click event handler.\r\n";
        };
    }

    async Task ExampleMethodAsync()
    {
        // The following line simulates a task-returning asynchronous process.
        await Task.Delay(1000);
    }
}

Další informace o způsobu vytváření a používání asynchronních metod naleznete v tématu Asynchronní programování pomocí modifikátoru Async a operátoru Await (C# a Visual Basic).

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

Mnoho standardních operátorů dotazu má vstupní parametr, jehož typ je jednou z řady obecných delegátů Func.Tyto delegáty používají parametry typu pro definování počtu a typů vstupních parametrů a návratový typ delegátu.Delegáty Func jsou velmi užitečné pro zapouzdření výrazů definovaných uživatelem, které jsou použity pro každý prvek v sadě zdrojových dat.Zvažte například následující typ delegátu:

public delegate TResult Func<TArg0, TResult>(TArg0 arg0)

Delegát může být vytvořen jako instance Func<int,bool> myFunc, kde int je vstupní parametr a bool je návratová hodnota.Vrácená hodnota je vždy určena v posledním parametru typu.Func<int, string, bool> definuje delegát se dvěma vstupními parametry, int a string, a návratovým typem bool.Následující delegát Func vrátí při vyvolání hodnotu true nebo false, pomocí které označí, zda je vstupní parametr roven hodnotě 5:

Func<int, bool> myFunc = x => x == 5;
bool result = myFunc(4); // returns false of course

Výraz lambda lze také zadat, pokud je typ argumentu Expression<Func>, například ve standardních operátorech dotazu, které jsou definovány ve třídě System.Linq.Queryable.Při zadání argumentu Expression<Func> bude lambda kompilována na strom výrazu.

Standardní operátor dotazu, metoda Count``1, je znázorněna zde:

int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
int oddNumbers = numbers.Count(n => n % 2 == 1);

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

Následující řádek kódu vytvoří posloupnost, která obsahuje všechny prvky v poli numbers, které jsou nalevo od hodnoty 9, protože se jedná o první číslo v sekvenci, která nesplňuje podmínku:

var firstNumbersLessThan6 = numbers.TakeWhile(n => n < 6);

Tento příklad znázorňuje způsob zadávání většího počtu vstupních parametrů uzavřením do závorek.Metoda vrátí všechny prvky v poli čísel, dokud není zjištěno číslo, jehož hodnota je nižší než jeho pozice.Nezaměňujte operátor lambda (=>) s operátorem „větší než“ nebo „rovno“ (>=).

var firstSmallNumbers = numbers.TakeWhile((n, index) => n >= index);

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ě hlavní části výrazu lambda, typu delegátu parametru 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 tedy dotazujete na IEnumerable<Customer>, pak je vstupní proměnná odvozena jako objekt Customer, což znamená, že máte přístup k jejím metodám a vlastnostem:

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

Obecná pravidla 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ýrazy lambda nemají vlastní určený typ, protože systém běžných typů nezná vnitřní pojem „výraz lambda“. Někdy je však vhodné hovořit neformálně o „typu“ výrazu lambda.V těchto případech typ odkazuje na typ delegátu nebo na typ Expression, na který je převeden výraz lambda.

Rozsah proměnné ve výrazech lambda

Výrazy Lambda mohou odkazovat na vnější proměnné (viz část Anonymní metody (Průvodce programováním v C#)), které jsou v rozsahu metody, jenž definuje funkci lambda, nebo v rozsahu 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:

delegate bool D();
delegate bool D2(int i);

class Test
{
    D del;
    D2 del2;
    public void TestMethod(int input)
    {
        int j = 0;
        // Initialize the delegates with lambda expressions.
        // Note access to 2 outer variables.
        // del will be invoked within this method.
        del = () => { j = 10;  return j > input; };

        // del2 will be invoked after TestMethod goes out of scope.
        del2 = (x) => {return x == j; };
      
        // Demonstrate value of j:
        // Output: j = 0 
        // The delegate has not been invoked yet.
        Console.WriteLine("j = {0}", j);        // Invoke the delegate.
        bool boolResult = del();

        // Output: j = 10 b = True
        Console.WriteLine("j = {0}. b = {1}", j, boolResult);
    }

    static void Main()
    {
        Test test = new Test();
        test.TestMethod(5);

        // Prove that del2 still has a copy of
        // local variable j from TestMethod.
        bool result = test.del2(10);

        // Output: True
        Console.WriteLine(result);
           
        Console.ReadKey();
    }
}

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é, které jsou představeny v rámci výrazu lambda, nejsou viditelné ve vnější metodě.

  • Výraz lambda nemůže přímo zachytit parametr ref nebo out z ohraničující metody.

  • Příkaz return ve výrazu lambda nezpůsobí vrácení ohraničující metody.

  • Výraz lambda nemůže obsahovat příkaz goto, break nebo continue, který se nachází uvnitř funkce lambda, pokud cíl příkazu skoku je mimo blok.Je také chybou mít příkaz skoku mimo blok funkce lambda, pokud je cíl uvnitř bloku.

Specifikace jazyka C#

Další informace najdete v tématu Specifikace jazyka C#. Specifikace jazyka je úplným a rozhodujícím zdrojem pro syntaxi a použití jazyka C#.

Doporučená kapitola knihy

Delegates, Events, and Lambda ExpressionsC# 3.0 Cookbook, Third Edition: More than 250 solutions for C# 3.0 programmers

Viz také

Referenční dokumentace

Anonymní metody (Průvodce programováním v C#)

is (Referenční dokumentace jazyka C#)

Koncepty

Průvodce programováním v C#

Stromy výrazů (C# a Visual Basic)

Další zdroje

LINQ (Language-Integrated Query)

Ukázky jazyka C# pro sadu Visual Studio 2008 (viz ukázkové soubory dotazů LINQ a program XQuery)

Rekurzivní výrazy lambda