Wyrażenia lambda (Przewodnik programowania w języku C#)

Wyrażenie lambda to funkcja anonimowa służąca do tworzenia typów delegatów lub typów drzewa wyrażeń.Za pomocą wyrażenia lambda można pisać funkcje lokalne, które mogą być przekazywane jako argumenty lub zwracane jako wartość wywołania funkcji.Wyrażenia lambda są szczególnie przydatne w przypadku pisania wyrażeń zapytań w języku LINQ.

Aby utworzyć wyrażenie lambda, należy określić parametry wejściowe (jeśli istnieją) po lewej stronie operatora lambda => i umieścić blok wyrażenia lub instrukcji po jego drugiej stronie.Na przykład wyrażenie lambda x => x * x określa parametr o nazwie x i zwraca wartość x podniesioną do kwadratu.To wyrażenie można przypisać do typu delegata, tak jak pokazano w poniższym przykładzie:

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

Aby utworzyć typ drzewa wyrażeń:

using System.Linq.Expressions;

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

Operator => ma takie samo pierwszeństwo jak operator przypisania (=) i ma łączność do prawej strony (zobacz sekcję „Łączność” w artykule Operatory).

Wyrażenia lambda są używane w zapytaniach w języku LINQ opartych na metodach jako argumenty dla metod standardowych operatorów zapytań, takich jak Where``1.

Gdy oparta na metodach składnia jest używana w celu wywołania metody Where``1 w klasie Enumerable (jak to ma miejsce w języku LINQ w odniesieniu do obiektów i typu LINQ do XML), parametr jest parametrem typu delegata Func.Użycie wyrażenia lambda jest najwygodniejszym sposobem tworzenia delegata.Po wywołaniu tej samej metody na przykład w klasie Queryable (tak jak w programie LINQ do SQL) jako typ parametru jest ustawiany typ Expression<Func>, gdzie Func jest dowolnym delegatem funkcji o długości do szesnastu parametrów wejściowych.I znowu wyrażenie lambda jest tylko bardzo zwięzłym sposobem konstruowania takiego drzewa wyrażeń.Dzięki wyrażeniom lambda wywołania funkcji Where wyglądają podobnie, choć w rzeczywistości typ obiektu utworzonego na podstawie wyrażenia lambda jest inny.

Należy zauważyć, że w poprzednim przykładzie podpis delegata ma jeden niejawnie wpisany parametr wejściowy typu int i zwraca wartość typu int.Wyrażenie lambda można przekonwertować na delegata tego typu, ponieważ ma on także jeden parametr wejściowy (x) i wartość zwracaną, którą kompilator może niejawnie przekonwertować na typ int. (Wnioskowanie typów omówiono bardziej szczegółowo w następnych sekcjach). Kiedy delegat jest wywołany za pomocą parametru wejściowego 5, zwraca wynik 25.

Wyrażenia lambda nie są dozwolone po lewej stronie operatorów is i as.

Wszystkie ograniczenia, które dotyczą metod anonimowych, dotyczą także wyrażeń lambda.Aby uzyskać więcej informacji, zobacz Metody anonimowe (Przewodnik programowania w języku C#).

Lambdy wyrażeń

Wyrażenie lambda z wyrażeniem po prawej stronie operatora => jest nazywane lambdą wyrażenia.Lambdy wyrażeń są często używane do tworzenia obiektów Drzewa wyrażeń (C# i Visual Basic).Lambda wyrażenia zwraca wynik wyrażenia i ma następującą podstawową formę:

(input parameters) => expression

Nawiasy są opcjonalne tylko wtedy, gdy lambda ma jeden parametr wejściowy; w przeciwnym razie są wymagane.Jeśli liczba parametrów wejściowych wynosi dwa lub więcej, te parametry są rozdzielane przecinkami i umieszczone w nawiasach:

(x, y) => x == y

Czasami wywnioskowanie typów wejściowych jest dla kompilatora trudne lub wręcz niemożliwe.W takiej sytuacji można jawnie określić typy, tak jak pokazano w poniższym przykładzie:

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

Określanie braku parametrów wejściowych za pomocą pustych nawiasów:

() => SomeMethod()

Należy zauważyć, że w poprzednim przykładzie treść wyrażenia lambda może składać się z wywołania metody.Jednak w przypadku tworzenia drzew wyrażeń, które będą obliczane poza programem .NET Framework, na przykład w programie SQL Server, nie należy używać wywołań metod w wyrażeniach lambda.Te metody nie będą zrozumiałe poza kontekstem środowiska uruchomieniowego języka wspólnego platformy .NET.

Lambdy instrukcji

Lambda instrukcji jest podobna do lambdy wyrażenia, z tym że instrukcje są ujęte w nawiasy klamrowe:

(input parameters) => {statement;}

Treść lambdy instrukcji może składać się z dowolnej liczby instrukcji, jednak w praktyce jest ich zwykle nie więcej niż dwie lub trzy.

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

Lambd instrukcji, podobnie jak metod anonimowych, nie można używać do tworzenia drzew wyrażeń.

Lambdy asynchroniczne

Możesz łatwo tworzyć wyrażenia lambda i instrukcje, które obejmują przetwarzanie asynchroniczne, używając słów kluczowych async i await.Na przykład poniższy przykład dla programu Windows Forms zawiera program obsługi zdarzeń, który wywołuje i czeka na metodę asynchroniczną 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);
    }
}

Można dodać ten sam program obsługi zdarzeń, używając lambdy asynchronicznej.Aby dodać ten program obsługi, należy dodać modyfikator async przed listą parametrów lambda, tak jak pokazano w poniższym przykładzie.

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

Aby uzyskać więcej informacji dotyczących tworzenia i używania metod asynchronicznych, zobacz Programowanie asynchroniczne z Async i Await (C# i Visual Basic).

Lambdy ze standardowymi operatorami zapytań

Wiele standardowych operatorów zapytań ma parametr wejściowy, którego typem jest jeden z rodziny ogólnych delegatów Func.Te delegaty używają parametrów typu użycia, aby zdefiniować liczbę i typy parametrów wejściowych oraz zwracany typ delegata.Delegaty Func są bardzo przydatne w przypadku hermetyzowania wyrażeń zdefiniowanych przez użytkownika, które są stosowane do każdego elementu w zestawie danych źródłowych.Na przykład rozważmy następujący typ delegata:

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

Wystąpienie delegata może zostać utworzone w postaci Func<int,bool> myFunc, gdzie int to parametr wejściowy, a bool to wartość zwracana.Wartość zwracana jest zawsze określona w ostatnim parametrze typu.Kod Func<int, string, bool> definiuje delegata z dwoma parametrami wejściowymi (int i string) i zwracanym typem bool.Poniższy delegat Func, gdy jest wywoływany, zwróci wartość true lub false, aby wskazać, czy parametr wejściowy jest równy 5:

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

Można również dostarczyć wyrażenie lambda, gdy typem argumentu jest Expression<Func>, na przykład w standardowych operatorach zapytań zdefiniowanych w typie System.Linq.Queryable.Po określeniu argumentu Expression<Func> lambda będzie kompilowana do drzewa wyrażeń.

Standardowy operator zapytania — metoda Count``1 — jest pokazany tutaj:

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

Kompilator może wywnioskować typ parametru wejściowego, ale można go również określić w sposób jawny.To konkretne wyrażenie lambda zlicza te liczby całkowite (n), które podzielone przez dwa dają resztę 1.

Poniższy wiersz kodu tworzy sekwencję zawierającą wszystkie elementy w tablicy numbers, które znajdują się po lewej stronie cyfry 9, ponieważ jest to pierwsza liczba w sekwencji, która nie spełnia warunku:

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

W tym przykładzie pokazano, jak określić wiele parametrów danych wejściowych, umieszczając je w nawiasach.Metoda zwraca wszystkie elementy w tablicy liczb, dopóki nie napotka liczby, której wartość jest mniejsza niż jej pozycja.Nie należy mylić operatora lambda (=>) z operatorem „większe niż lub równe” (>=).

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

Wnioskowanie typów w wyrażeniach lambda

Podczas pisania wyrażeń lambda często nie trzeba określać typu parametrów wejściowych, ponieważ kompilator może wywnioskować typ na podstawie treści wyrażenia lambda, typu delegata parametru i innych czynników, tak jak opisano w specyfikacji języka C#.Dla większości standardowych operatorów zapytań pierwszy element danych wejściowych jest typem elementów w sekwencji źródłowej.Dlatego jeśli jest wykonywane zapytanie IEnumerable<Customer>, następuje wnioskowanie, że zmienna wejściowa jest obiektem typu Customer, co oznacza, że masz dostęp do jego metod i właściwości:

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

Ogólne reguły dotyczące wyrażeń lambda są następujące:

  • Wyrażenie lambda musi zawierać taką samą liczbę parametrów jak typ delegata.

  • Każdy parametr wejściowy w wyrażeniu lambda musi umożliwiać niejawną konwersję na odpowiadający mu parametr delegata.

  • Wartość zwracana wyrażenia lambda (jeżeli istnieje) musi umożliwiać niejawną konwersję na zwracany typ delegata.

Należy zauważyć, że wyrażenia lambda same w sobie nie mają typu, ponieważ system typów wspólnych nie obejmuje wewnętrznej koncepcji „wyrażenia lambda”. Jednak czasami wygodnie jest mówić potocznie o „typie” wyrażenia lambda.W tych przypadkach słowo „typ” odnosi się do typu delegata lub typu Expression, na który jest konwertowane wyrażenie lambda.

Zakres zmiennych w wyrażeniach lambda

Wyrażenia lambda mogą odwoływać się do zmiennych zewnętrznych (zobacz Metody anonimowe (Przewodnik programowania w języku C#)), które znajdują się w zakresie metody definiującej funkcję lambda lub w zakresie typu, który zawiera wyrażenie lambda.Przechwytywane w ten sposób zmienne są przechowywane do użytku w wyrażeniu lambda, nawet gdyby w innym wypadku te zmienne znalazłyby się poza zakresem i zostałyby usunięte w ramach odśmiecania pamięci.Zewnętrzna zmienna musi być zdecydowanie przypisana, aby można jej było użyć w wyrażeniu lambda.W poniższym przykładzie pokazano te reguły:

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

Do zakresu zmiennych w wyrażeniach lambda są stosowane następujące reguły:

  • Zmienna, która jest przechwytywana, nie będzie usuwana w ramach odśmiecania pamięci, dopóki odwołujący się do niej delegat nie będzie podlegał odśmiecaniu pamięci.

  • Zmienne zawarte w wyrażeniu lambda nie są widoczne w metodzie zewnętrznej.

  • Wyrażenie lambda nie może bezpośrednio przechwycić parametru ref ani out z obejmującej go metody.

  • Instrukcja return w wyrażeniu lambda nie powoduje wykonania instrukcji return w otaczającej go metodzie.

  • Wyrażenie lambda nie może zawierać instrukcji goto, break ani continue, która znajduje się wewnątrz funkcji lambda, jeśli obiekt docelowy instrukcji skoku znajduje się poza blokiem.Błędem jest również instrukcja skoku poza blok funkcji lambda, jeśli obiekt docelowy znajduje się wewnątrz bloku.

Specyfikacja języka C#

Aby uzyskać więcej informacji, zobacz Specyfikacja języka C#. Specyfikacja języka jest ostatecznym źródłem informacji o składni i użyciu języka C#.

Polecany rozdział książki

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

Zobacz też

Informacje

Metody anonimowe (Przewodnik programowania w języku C#)

is (odwołanie w C#)

Koncepcje

Przewodnik programowania w języku C#

Drzewa wyrażeń (C# i Visual Basic)

Inne zasoby

LINQ (zapytania o języku zintegrowanym)

Przykłady dla programu Visual Studio 2008 C# (zobacz sekcję Przykładowe pliki zapytań w języku LINQ i program XQuery)

Cykliczne wyrażenia lambda