Wyrażenia lambda (C# Przewodnik programowania)Lambda expressions (C# Programming Guide)

Wyrażenie lambda jest blokiem kodu (wyrażenie lub blok instrukcji), który jest traktowany jako obiekt.A lambda expression is a block of code (an expression or a statement block) that is treated as an object. Może być przekazana jako argument do metod i może być również zwracany przez wywołania metody.It can be passed as an argument to methods, and it can also be returned by method calls. Wyrażenia lambda są używane w szerokim stopniu dla:Lambda expressions are used extensively for:

Wyrażenia lambda to kod, który może być reprezentowany jako delegat lub jako drzewo wyrażenia, które kompiluje do delegata.Lambda expressions are code that can be represented either as a delegate, or as an expression tree that compiles to a delegate. Określony typ delegata wyrażenia lambda zależy od jego parametrów i wartości zwracanej.The specific delegate type of a lambda expression depends on its parameters and return value. Wyrażenia lambda, które nie zwracają wartości, odpowiadają określonemu Action delegatowi, w zależności od jego liczby parametrów.Lambda expressions that don't return a value correspond to a specific Action delegate, depending on its number of parameters. Wyrażenia lambda, które zwracają wartość, odpowiadają określonemu Func delegatowi, w zależności od jego liczby parametrów.Lambda expressions that return a value correspond to a specific Func delegate, depending on its number of parameters. Na przykład wyrażenie lambda, które ma dwa parametry, ale nie zwraca wartości odnosi się do Action<T1,T2> delegata.For example, a lambda expression that has two parameters but returns no value corresponds to an Action<T1,T2> delegate. Wyrażenie lambda, które ma jeden parametr i zwraca wartość odnosi się do Func<T,TResult> delegata.A lambda expression that has one parameter and returns a value corresponds to Func<T,TResult> delegate.

Wyrażenie lambda używa => operatora deklaracji lambda, aby oddzielić listę parametrów lambda z jego kodu wykonywalnego.A lambda expression uses =>, the lambda declaration operator, to separate the lambda's parameter list from its executable code. Aby utworzyć wyrażenie lambda, należy określić parametry wejściowe (jeśli istnieją) po lewej stronie operatora lambda, i umieścić wyrażenie lub blok instrukcji po drugiej stronie.To create a lambda expression, you specify input parameters (if any) on the left side of the lambda operator, and you put the expression or statement block on the other side. Na przykład jednowierszowe wyrażenie x => x * x lambda określa parametr o nazwie x x i zwraca wartość kwadratową.For example, the single-line lambda expression x => x * x specifies a parameter that’s named x and returns the value of x squared. To wyrażenie można przypisać do typu delegata, tak jak pokazano w poniższym przykładzie:You can assign this expression to a delegate type, as the following example shows:

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

Można również przypisać wyrażenie lambda do typu drzewa wyrażenia:You also can assign a lambda expression to an expression tree type:

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

Lub można przekazać ją bezpośrednio jako argument metody:Or you can pass it directly as a method argument:

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

Przy użyciu składni opartej na metodzie wywoływanie Enumerable.Select metody System.Linq.Enumerable w klasie (jak w LINQ to Objects i LINQ to XML) parametr jest typem System.Func<T,TResult>delegata.When you use method-based syntax to call the Enumerable.Select method in the System.Linq.Enumerable class (as you do in LINQ to Objects and LINQ to XML) the parameter is a delegate type System.Func<T,TResult>. Użycie wyrażenia lambda jest najwygodniejszym sposobem tworzenia delegata.A lambda expression is the most convenient way to create that delegate. Po wywołaniu Queryable.Select metody System.Linq.Queryable w klasie (jak w LINQ to SQL) typem parametru jest typ Expression<Func<TSource,TResult>>drzewa wyrażenia.When you call the Queryable.Select method in the System.Linq.Queryable class (as you do in LINQ to SQL) the parameter type is an expression tree type Expression<Func<TSource,TResult>>. I znowu wyrażenie lambda jest tylko bardzo zwięzłym sposobem konstruowania takiego drzewa wyrażeń.Again, a lambda expression is just a very concise way to construct that expression tree. Wyrażenia lambda umożliwiają Select podobne wywołania, chociaż w rzeczywistości typ obiektu utworzonego na podstawie wyrażenia lambda jest inny.The lambdas allow the Select calls to look similar although in fact the type of object created from the lambda is different.

Wyrażenia lambdaExpression lambdas

Wyrażenie lambda z wyrażeniem po prawej stronie => operatora jest nazywane wyrażeniem lambda.A lambda expression with an expression on the right side of the => operator is called an expression lambda. Wyrażenia lambda są szeroko używane w konstruowaniu drzew wyrażeń.Expression lambdas are used extensively in the construction of expression trees. Lambda wyrażenia zwraca wynik wyrażenia i ma następującą podstawową formę:An expression lambda returns the result of the expression and takes the following basic form:

(input-parameters) => expression

Nawiasy są opcjonalne tylko wtedy, gdy lambda ma jeden parametr wejściowy; w przeciwnym razie są wymagane.The parentheses are optional only if the lambda has one input parameter; otherwise they are required.

Określanie braku parametrów wejściowych za pomocą pustych nawiasów:Specify zero input parameters with empty parentheses:

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

Jeśli liczba parametrów wejściowych wynosi dwa lub więcej, te parametry są rozdzielane przecinkami i umieszczone w nawiasach:Two or more input parameters are separated by commas enclosed in parentheses:

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

Czasami w kompilatorze nie można wywnioskować typów wejściowych.Sometimes it's impossible for the compiler to infer the input types. Możesz określić typy jawnie, jak pokazano w następującym przykładzie:You can specify the types explicitly as shown in the following example:

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

Typy parametrów wejściowych muszą być jawne lub niejawne; w przeciwnym razie wystąpi błąd kompilatora CS0748 .Input parameter types must be all explicit or all implicit; otherwise, a CS0748 compiler error occurs.

Treść wyrażenia lambda może składać się z wywołania metody.The body of an expression lambda can consist of a method call. Jeśli jednak tworzysz drzewa wyrażeń, które są oceniane poza kontekstem środowiska uruchomieniowego języka wspólnego .NET, na przykład w SQL Server, nie należy używać wywołań metod w wyrażeniach lambda.However, if you are creating expression trees that are evaluated outside the context of the .NET common language runtime, such as in SQL Server, you should not use method calls in lambda expressions. Te metody nie będą zrozumiałe poza kontekstem środowiska uruchomieniowego języka wspólnego platformy .NET.The methods will have no meaning outside the context of the .NET common language runtime.

Instrukcja lambdaStatement lambdas

Lambda instrukcji jest podobna do lambdy wyrażenia, z tym że instrukcje są ujęte w nawiasy klamrowe:A statement lambda resembles an expression lambda except that the statement(s) is enclosed in braces:

(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.The body of a statement lambda can consist of any number of statements; however, in practice there are typically no more than two or three.

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

Instrukcji lambda nie można używać do tworzenia drzew wyrażeń.Statement lambdas cannot be used to create expression trees.

Asynchroniczne wyrażenia lambdaAsync lambdas

Możesz łatwo tworzyć wyrażenia lambda i instrukcje, które zawierają asynchroniczne przetwarzanie przy użyciu słów kluczowych Async i await .You can easily create lambda expressions and statements that incorporate asynchronous processing by using the async and await keywords. Na przykład poniższy Windows Forms przykład zawiera procedurę obsługi zdarzeń, która wywołuje i czeka na metodę ExampleMethodAsyncasynchroniczną.For example, the following Windows Forms example contains an event handler that calls and awaits an async method, 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);
    }
}

Można dodać ten sam program obsługi zdarzeń, używając lambdy asynchronicznej.You can add the same event handler by using an async lambda. Aby dodać ten program obsługi, Dodaj async modyfikator przed listą parametrów lambda, jak pokazano na poniższym przykładzie:To add this handler, add an async modifier before the lambda parameter list, as the following example shows:

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

Aby uzyskać więcej informacji na temat tworzenia i używania metod asynchronicznych, zobacz programowanie asynchroniczne z Async i await.For more information about how to create and use async methods, see Asynchronous Programming with async and await.

Wyrażenia lambda i krotkiLambda expressions and tuples

Począwszy od C# 7,0, C# język zapewnia wbudowaną obsługę krotek.Starting with C# 7.0, the C# language provides built-in support for tuples. Można podać krotkę jako argument wyrażenia lambda, a wyrażenie lambda może również zwracać krotkę.You can provide a tuple as an argument to a lambda expression, and your lambda expression can also return a tuple. W niektórych przypadkach C# kompilator używa wnioskowania o typie, aby określić typy składników spójnych kolekcji.In some cases, the C# compiler uses type inference to determine the types of tuple components.

Można zdefiniować krotkę, umieszczając w nawiasach rozdzielaną przecinkami listę składników.You define a tuple by enclosing a comma-delimited list of its components in parentheses. Poniższy przykład używa krotki z trzema składnikami, aby przekazać sekwencję liczb do wyrażenia lambda, które podwaja każdą wartość i zwraca spójną kolekcję z trzema składnikami, które zawierają wynik mnożenia.The following example uses tuple with three components to pass a sequence of numbers to a lambda expression, which doubles each value and returns a tuple with three components that contains the result of the multiplications.

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)

Zwykle pola krotki mają nazwę Item1, Item2itp. Można jednak zdefiniować krotkę z nazwanymi składnikami, tak jak w poniższym przykładzie.Ordinarily, the fields of a tuple are named Item1, Item2, etc. You can, however, define a tuple with named components, as the following example does.

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

Aby uzyskać więcej informacji C# na temat krotek, zobacz C# typy krotek.For more information about C# tuples, see C# tuple types.

Wyrażenia lambda ze standardowymi operatorami zapytańLambdas with the standard query operators

LINQ to Objects, między innymi implementacjami, mają parametr wejściowy, którego typ jest jedną Func<TResult> z rodziny delegatów ogólnych.LINQ to Objects, among other implementations, have an input parameter whose type is one of the Func<TResult> family of generic delegates. Te Delegaty używają parametrów typu, aby zdefiniować liczbę i typ parametrów wejściowych oraz zwracany typ delegata.These delegates use type parameters to define the number and type of input parameters, and the return type of the delegate. FuncDelegaty są bardzo przydatne do hermetyzowania wyrażeń zdefiniowanych przez użytkownika, które są stosowane do każdego elementu w zestawie danych źródłowych.Func delegates are very useful for encapsulating user-defined expressions that are applied to each element in a set of source data. Rozważmy na przykład typ Func<T,TResult> delegata:For example, consider the Func<T,TResult> delegate type:

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

Delegat można utworzyć jako Func<int, bool> wystąpienie, gdzie int jest parametrem wejściowym i bool jest wartością zwracaną.The delegate can be instantiated as a Func<int, bool> instance where int is an input parameter and bool is the return value. Wartość zwracana jest zawsze określona w ostatnim parametrze typu.The return value is always specified in the last type parameter. Na przykład Func<int, string, bool> definiuje delegata z dwoma int parametrami wejściowymi i stringi typem boolzwracanym.For example, Func<int, string, bool> defines a delegate with two input parameters, int and string, and a return type of bool. Następujący Func delegat, gdy jest wywoływany, zwraca wartość logiczną, która wskazuje, czy parametr wejściowy jest równy pięć:The following Func delegate, when it's invoked, returns Boolean value that indicates whether the input parameter is equal to five:

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

Można również podać wyrażenie lambda Expression<TDelegate>, gdy typem argumentu jest, na przykład w standardowych operatorów zapytań, które są zdefiniowane Queryable w typie.You can also supply a lambda expression when the argument type is an Expression<TDelegate>, for example in the standard query operators that are defined in the Queryable type. Po określeniu Expression<TDelegate> argumentu lambda jest kompilowane do drzewa wyrażenia.When you specify an Expression<TDelegate> argument, the lambda is compiled to an expression tree.

W poniższym przykładzie użyto Count standardowego operatora zapytania:The following example uses the Count standard query operator:

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

Kompilator może wywnioskować typ parametru wejściowego, ale można go również określić w sposób jawny.The compiler can infer the type of the input parameter, or you can also specify it explicitly. To konkretne wyrażenie lambda liczy te liczby całkowite (n), które podzielone przez dwa mają resztę 1.This particular lambda expression counts those integers (n) which when divided by two have a remainder of 1.

Poniższy przykład tworzy sekwencję zawierającą wszystkie elementy w numbers tablicy, która poprzedza 9, ponieważ jest to pierwszy numer w sekwencji, która nie spełnia warunku:The following example produces a sequence that contains all elements in the numbers array that precede the 9, because that's the first number in the sequence that doesn't meet the condition:

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

W poniższym przykładzie określono wiele parametrów wejściowych, umieszczając je w nawiasach.The following example specifies multiple input parameters by enclosing them in parentheses. Metoda zwraca wszystkie elementy w numbers tablicy do momentu napotkania liczby, której wartość jest mniejsza niż jej pozycja porządkowa w tablicy:The method returns all the elements in the numbers array until it encounters a number whose value is less than its ordinal position in the array:

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

Wnioskowanie o typie w wyrażeniach lambdaType inference in lambda expressions

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, typów parametrów i innych czynników, zgodnie z opisem w specyfikacji C# języka.When writing lambdas, you often don't have to specify a type for the input parameters because the compiler can infer the type based on the lambda body, the parameter types, and other factors as described in the C# language specification. Dla większości standardowych operatorów zapytań pierwszy element danych wejściowych jest typem elementów w sekwencji źródłowej.For most of the standard query operators, the first input is the type of the elements in the source sequence. Jeśli wykonujesz zapytania IEnumerable<Customer>, wówczas zmienna wejściowa jest wywnioskowana Customer jako obiekt, co oznacza, że masz dostęp do jego metod i właściwości:If you are querying an IEnumerable<Customer>, then the input variable is inferred to be a Customer object, which means you have access to its methods and properties:

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

Ogólne reguły wnioskowania o typie dla wyrażeń lambda są następujące:The general rules for type inference for lambdas are as follows:

  • Wyrażenie lambda musi zawierać taką samą liczbę parametrów jak typ delegata.The lambda must contain the same number of parameters as the delegate type.

  • Każdy parametr wejściowy w wyrażeniu lambda musi umożliwiać niejawną konwersję na odpowiadający mu parametr delegata.Each input parameter in the lambda must be implicitly convertible to its corresponding delegate parameter.

  • Wartość zwracana wyrażenia lambda (jeżeli istnieje) musi umożliwiać niejawną konwersję na zwracany typ delegata.The return value of the lambda (if any) must be implicitly convertible to the delegate's return type.

Należy zauważyć, że wyrażenia lambda same w sobie nie mają typu, ponieważ system typów wspólnych nie ma wewnętrznej koncepcji "wyrażenie lambda".Note that lambda expressions in themselves don't have a type because the common type system has no intrinsic concept of "lambda expression." Jednak czasami wygodnie jest mówić nieformalnie "Type" wyrażenia lambda.However, it's sometimes convenient to speak informally of the "type" of a lambda expression. W takich przypadkach typ odwołuje się do typu delegata Expression lub typu, do którego wyrażenie lambda jest konwertowane.In these cases the type refers to the delegate type or Expression type to which the lambda expression is converted.

Przechwycenie zmiennych zewnętrznych i zakresu zmiennych w wyrażeniach lambdaCapture of outer variables and variable scope in lambda expressions

Wyrażenia lambda mogą odwoływać się do zmiennych zewnętrznych.Lambdas can refer to outer variables. Są to zmienne, które znajdują się w zakresie metody, która definiuje wyrażenie lambda lub w zakresie typu, który zawiera wyrażenie lambda.These are the variables that are in scope in the method that defines the lambda expression, or in scope in the type that contains the lambda expression. 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.Variables that are captured in this manner are stored for use in the lambda expression even if the variables would otherwise go out of scope and be garbage collected. Zewnętrzna zmienna musi być zdecydowanie przypisana, aby można jej było użyć w wyrażeniu lambda.An outer variable must be definitely assigned before it can be consumed in a lambda expression. W poniższym przykładzie pokazano te reguły:The following example demonstrates these rules:

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
}

Do zakresu zmiennych w wyrażeniach lambda są stosowane następujące reguły:The following rules apply to variable scope in lambda expressions:

  • 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.A variable that is captured will not be garbage-collected until the delegate that references it becomes eligible for garbage collection.

  • Zmienne wprowadzone w wyrażeniu lambda nie są widoczne w otaczającej metodzie.Variables introduced within a lambda expression are not visible in the enclosing method.

  • Wyrażenie lambda nie może bezpośrednio przechwycić parametru in, reflub out z otaczającej metody.A lambda expression cannot directly capture an in, ref, or out parameter from the enclosing method.

  • Instrukcja Return w wyrażeniu lambda nie powoduje zwrócenia otaczającej metody.A return statement in a lambda expression doesn't cause the enclosing method to return.

  • Wyrażenie lambda nie może zawierać instrukcji goto, Breakani Continue , jeśli element docelowy instrukcji skoku znajduje się poza blokiem wyrażenia lambda.A lambda expression cannot contain a goto, break, or continue statement if the target of that jump statement is outside the lambda expression block. Występuje również błąd instrukcji skoku poza blokiem wyrażenia lambda, jeśli obiekt docelowy znajduje się wewnątrz bloku.It's also an error to have a jump statement outside the lambda expression block if the target is inside the block.

specyfikacja języka C#C# language specification

Aby uzyskać więcej informacji, zobacz sekcję wyrażenia funkcji anonimowej C# specyfikacji języka.For more information, see the Anonymous function expressions section of the C# language specification.

Delegaty, zdarzenia i wyrażenia lambda w C# 3,0 Cookbook, wydanie trzecie: Ponad 250 rozwiązań dla C# programistów 3,0Delegates, Events, and Lambda Expressions in C# 3.0 Cookbook, Third Edition: More than 250 solutions for C# 3.0 programmers

Zobacz takżeSee also