Лямбда-выражения (Руководство по программированию в C#)Lambda Expressions (C# Programming Guide)

Лямбда-выражение — это анонимная функция , с помощью которой можно создавать типы делегатов или деревьев выражений .A lambda expression is an anonymous function that you can use to create delegates or expression tree types. С помощью лямбда-выражений можно писать локальные функции, которые можно передавать в качестве аргументов или возвращать в качестве значений из вызовов функций.By using lambda expressions, you can write local functions that can be passed as arguments or returned as the value of function calls. Лямбда-выражения особенно полезны при написании выражений запросов LINQ.Lambda expressions are particularly helpful for writing LINQ query expressions.

Чтобы создать лямбда-выражение, необходимо указать входные параметры (если они есть) с левой стороны лямбда-оператора =>, и поместить блок выражений или операторов с другой стороны.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. Например, лямбда-выражение x => x * x задает параметр с именем x и возвращает значение x .For example, the lambda expression x => x * x specifies a parameter that’s named x and returns the value of x squared. Можно назначить это выражение типу делегата, как показано в следующем примере:You can assign this expression to a delegate type, as the following example shows:

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

Создание типа дерева выражений:To create an expression tree type:

using System.Linq.Expressions;  

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

Оператор => имеет такой же приоритет, как и присваивание (=), и является правоассоциативным (см. раздел "Ассоциативность" статьи об операторах).The => operator has the same precedence as assignment (=) and is right associative (see "Associativity" section of the Operators article).

Лямбда-операторы используются в запросах LINQLINQ на основе методов в качестве аргументов стандартных методов операторов запроса, таких как Where.Lambdas are used in method-based LINQLINQ queries as arguments to standard query operator methods such as Where.

При использовании синтаксиса на основе методов для вызова метода Where в классе Enumerable (как это делается в LINQLINQ на объекты и LINQ to XMLLINQ to XML) параметром является тип делегата System.Func<T,TResult>.When you use method-based syntax to call the Where method in the Enumerable class (as you do in LINQLINQ to Objects and LINQ to XMLLINQ to XML) the parameter is a delegate type System.Func<T,TResult>. Лямбда-выражение — это наиболее удобный способ создания делегата.A lambda expression is the most convenient way to create that delegate. При вызове того же метода, к примеру, в классе System.Linq.Queryable (как это делается в LINQ to SQLLINQ to SQL) типом параметра будет System.Linq.Expressions.Expression<Func>, где Func — это любые делегаты Func с числом входных параметров не более шестнадцати.When you call the same method in, for example, the System.Linq.Queryable class (as you do in LINQ to SQLLINQ to SQL) then the parameter type is an System.Linq.Expressions.Expression<Func> where Func is any of the Func delegates with up to sixteen input parameters. Опять же, лямбда-выражения представляют собой самый быстрый способ построения дерева выражений.Again, a lambda expression is just a very concise way to construct that expression tree. Лямбда-выражения позволяют вызовам Where выглядеть одинаково, хотя на самом деле объект, созданный из лямбда-выражения, имеет другой тип.The lambdas allow the Where calls to look similar although in fact the type of object created from the lambda is different.

Обратите внимание: в приведенном выше примере сигнатура делегата имеет один неявный входной параметр типа intи возвращает значение типа int.In the previous example, notice that the delegate signature has one implicitly-typed input parameter of type int, and returns an int. Лямбда-выражение можно преобразовать в делегат соответствующего типа, поскольку он также имеет один входной параметр (x) и возвращает значение, которое компилятор может неявно преобразовать в тип int.The lambda expression can be converted to a delegate of that type because it also has one input parameter (x) and a return value that the compiler can implicitly convert to type int. (Вывод типов более подробно рассматривается в следующих разделах.) Делегат, вызываемый посредством входного параметра 5, возвращает результат 25.(Type inference is discussed in more detail in the following sections.) When the delegate is invoked by using an input parameter of 5, it returns a result of 25.

Лямбда-выражения нельзя использовать с левой стороны оператора is или as.Lambdas are not allowed on the left side of the is or as operator.

Все ограничения, применяемые к анонимным методам, применяются также к лямбда-выражениям.All restrictions that apply to anonymous methods also apply to lambda expressions. Дополнительные сведения см. в разделе Анонимные методы.For more information, see Anonymous Methods.

Выражения-лямбдыExpression Lambdas

Лямбда-выражение с выражением с правой стороны оператора => называется выражением-лямбдой.A lambda expression with an expression on the right side of the => operator is called an expression lambda. Выражения-лямбды широко используются при конструировании деревьев выражений.Expression lambdas are used extensively in the construction of Expression Trees. Выражения-лямбды возвращают результат выражения и принимают следующую основную форму.An expression lambda returns the result of the expression and takes the following basic form:

(input-parameters) => expression

Если лямбда-выражение имеет только один входной параметр, скобки можно не ставить; во всех остальных случаях они обязательны.The parentheses are optional only if the lambda has one input parameter; otherwise they are required. Два и более входных параметра разделяются запятыми и заключаются в скобки:Two or more input parameters are separated by commas enclosed in parentheses:

(x, y) => x == y

Иногда компилятору бывает трудно или даже невозможно определить входные типы.Sometimes it is difficult or impossible for the compiler to infer the input types. В этом случае типы можно указать в явном виде, как показано в следующем примере.When this occurs, you can specify the types explicitly as shown in the following example:

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

Типы входных параметров должны быть либо полностью явными, либо полностью неявными; в противном случае C# создает ошибку компилятора CS0748.Input paramater types must be all explicit or all implicit; otherwise, C# generates a CS0748 compiler error.

Нулевое количество входных параметров задается пустыми скобками:Specify zero input parameters with empty parentheses:

() => SomeMethod()

Обратите внимание, что тело выражения-лямбды может состоять из вызова метода, как было показано в предыдущем примере.Note in the previous example that the body of an expression lambda can consist of a method call. Однако при создании деревьев выражений, которые вычисляются вне .NET Framework, например в SQL Server, не следует использовать вызовы методов в лямбда-выражениях.However, if you are creating expression trees that are evaluated outside of the .NET Framework, such as in SQL Server, you should not use method calls in lambda expressions. Эти методы не имеют смысла вне контекста среды CLR .NET.The methods will have no meaning outside the context of the .NET common language runtime.

Лямбды операторовStatement Lambdas

Лямбда оператора напоминает выражение-лямбду, за исключением того, что оператор (или операторы) заключается в фигурные скобки:A statement lambda resembles an expression lambda except that the statement(s) is enclosed in braces:

(входные-параметры) => { оператор; }(input-parameters) => { statement; }

Тело лямбды оператора может состоять из любого количества операторов; однако на практике обычно используется не более двух-трех.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.

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

Лямбды операторов, как и анонимные методы, не могут использоваться для создания деревьев выражений.Statement lambdas, like anonymous methods, cannot be used to create expression trees.

Асинхронные лямбда-выраженияAsync Lambdas

С помощью ключевых слов async и await можно легко создавать лямбда-выражения и операторы, включающие асинхронную обработку.You can easily create lambda expressions and statements that incorporate asynchronous processing by using the async and await keywords. Например, в следующем примере Windows Forms содержится обработчик событий, который вызывает асинхронный метод ExampleMethodAsyncи ожидает его.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();  
    }  

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

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

Такой же обработчик событий можно добавить с помощью асинхронного лямбда-выражения.You can add the same event handler by using an async lambda. Чтобы добавить этот обработчик, поставьте модификатор async перед списком параметров лямбда-выражения, как показано в следующем примере.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) =>  
        {  
            // ExampleMethodAsync returns a Task.  
            await ExampleMethodAsync();  
            textBox1.Text += "\nControl returned to Click event handler.\n";  
        };  
    }  

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

Дополнительные сведения о создании и использовании асинхронных методов см. в разделе Асинхронное программирование с использованием ключевых слов Async и Await.For more information about how to create and use async methods, see Asynchronous Programming with async and await.

Лямбды со стандартными операторами запросовLambdas with the Standard Query Operators

Многие стандартные операторы запросов имеют входной параметр, тип которого принадлежит к семейству Func<T,TResult> универсальных делегатов.Many Standard query operators have an input parameter whose type is one of the Func<T,TResult> family of generic delegates. Эти делегаты используют параметры типа для определения количества и типов входных параметров, а также тип возвращаемого значения делегата.These delegates use type parameters to define the number and types of input parameters, and the return type of the delegate. ДелегатыFunc очень полезны для инкапсуляции пользовательских выражений, которые применяются к каждому элементу в наборе исходных данных.Func delegates are very useful for encapsulating user-defined expressions that are applied to each element in a set of source data. В качестве примера рассмотрим следующий тип делегата.For example, consider the following delegate type:

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

Экземпляр этого делегата можно создать как Func<int,bool> myFunc , где int — входной параметр, а bool — возвращаемое значение.The delegate can be instantiated as Func<int,bool> myFunc where int is an input parameter and bool is the return value. Возвращаемое значение всегда указывается в последнем параметре типа.The return value is always specified in the last type parameter. Func<int, string, bool> определяет делегат с двумя входными параметрами, int и string, и типом возвращаемого значения bool.Func<int, string, bool> defines a delegate with two input parameters, int and string, and a return type of bool. Следующий делегат Func при вызове возвращает значение true или false, которое показывает, равен ли входной параметр 5.The following Func delegate, when it is invoked, will return true or false to indicate whether the input parameter is equal to 5:

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

Также лямбда-выражения можно использовать, когда аргумент имеет тип Expression<Func>, например в стандартных операторах запросов, как указано в System.Linq.Queryable.You can also supply a lambda expression when the argument type is an Expression<Func>, for example in the standard query operators that are defined in System.Linq.Queryable. При определении аргумента Expression<Func> лямбда компилируется в дерево выражений.When you specify an Expression<Func> argument, the lambda will be compiled to an expression tree.

Ниже показан метод Count , являющийся стандартным оператором запроса.A standard query operator, the Count method, is shown here:

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

Компилятор может вывести тип входного параметра ввода; но его также можно определить явным образом.The compiler can infer the type of the input parameter, or you can also specify it explicitly. Данное лямбда-выражение подсчитывает указанные целые значения (n), которые при делении на два дают остаток 1.This particular lambda expression counts those integers (n) which when divided by two have a remainder of 1.

Следующая строка кода создает последовательность, которая содержит все элементы массива numbers , расположенные слева от 9, поскольку это первое число последовательности, не удовлетворяющее условию:The following line of code produces a sequence that contains all elements in the numbers array that are to the left side of the 9 because that's the first number in the sequence that doesn't meet the condition:

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

В этом примере показано, как определить несколько входных параметров путем их заключения в скобки.This example shows how to specify multiple input parameters by enclosing them in parentheses. Этот метод возвращает все элементы в массиве чисел до того числа, величина которого меньше номера его позиции.The method returns all the elements in the numbers array until a number is encountered whose value is less than its position. Не следует путать лямбда-оператор (=>) с оператором "больше или равно" (>=).Do not confuse the lambda operator (=>) with the greater than or equal operator (>=).

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

Вывод типа в лямбда-выраженияхType Inference in Lambdas

При написании лямбда-выражений обычно не требуется указывать тип входных параметров, поскольку компилятор может выводить этот тип на основе тела лямбда-выражения, типа делегата параметра и других факторов, как описано в спецификации языка C#.When writing lambdas, you often do not have to specify a type for the input parameters because the compiler can infer the type based on the lambda body, the parameter’s delegate type, and other factors as described in the C# Language Specification. Для большинства стандартных операторов запросов первой входное значение имеет тип элементов в исходной последовательности.For most of the standard query operators, the first input is the type of the elements in the source sequence. Поэтому при запросе IEnumerable<Customer>входная переменная считается объектом Customer , а это означает, что у вас есть доступ к его методам и свойствам.So 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");  

Общие правила для лямбда-выражений формулируются следующим образом:The general rules for lambdas are as follows:

  • лямбда-выражение должно содержать то же число параметров, что и тип делегата;The lambda must contain the same number of parameters as the delegate type.

  • каждый входной параметр в лямбда-выражении должен быть неявно преобразуемым в соответствующий параметр делегата;Each input parameter in the lambda must be implicitly convertible to its corresponding delegate parameter.

  • возвращаемое значение лямбда-выражения (если таковое имеется) должно быть неявно преобразуемым в возвращаемый тип делегата.The return value of the lambda (if any) must be implicitly convertible to the delegate's return type.

Обратите внимание: лямбда-выражения сами по себе не имеют типа, поскольку в системе общих типов изначально отсутствует понятие "лямбда-выражения".Note that lambda expressions in themselves do not have a type because the common type system has no intrinsic concept of "lambda expression." Однако иногда бывает удобно оперировать понятием "типа" применительно к лямбда-выражениям.However, it is sometimes convenient to speak informally of the "type" of a lambda expression. При этом под типом понимается тип делегата или тип Expression , в который преобразуется лямбда-выражение.In these cases the type refers to the delegate type or Expression type to which the lambda expression is converted.

Область действия переменной в лямбда-выраженияхVariable Scope in Lambda Expressions

Лямбда-выражения могут ссылаться на внешние переменные (см. раздел Анонимные методы), находящиеся в области метода, в котором определена лямбда-функция, или в области типа, который содержит лямбда-выражение.Lambdas can refer to outer variables (see Anonymous Methods) that are in scope in the method that defines the lambda function, or in scope in the type that contains the lambda expression. Переменные, полученные таким способом, сохраняются для использования в лямбда-выражениях, даже если бы в ином случае они оказались за границами области действия и уничтожились сборщиком мусора.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. Внешняя переменная должна быть определенным образом присвоена, прежде чем она сможет использоваться в лямбда-выражениях.An outer variable must be definitely assigned before it can be consumed in a lambda expression. В следующем примере демонстрируются эти правила.The following example demonstrates these rules:

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

Следующие правила применимы к области действия переменной в лямбда-выражениях.The following rules apply to variable scope in lambda expressions:

  • Захваченная переменная не будет уничтожена сборщиком мусора до тех пор, пока делегат, который на нее ссылается, не перейдет в статус подлежащего уничтожению при сборке мусора.A variable that is captured will not be garbage-collected until the delegate that references it becomes eligible for garbage collection.

  • Переменные, вводимые в лямбда-выражении, невидимы во внешнем методе.Variables introduced within a lambda expression are not visible in the outer method.

  • Лямбда-выражение не может непосредственно захватывать параметры in, ref или out из метода, в котором они находятся.A lambda expression cannot directly capture an in, ref, or out parameter from an enclosing method.

  • Оператор return в лямбда-выражении не вызывает возвращение значения внешним методом.A return statement in a lambda expression does not cause the enclosing method to return.

  • Лямбда-выражение не может содержать оператора goto , оператора break или оператора continue внутри лямбда-функции, если целевой объект перехода находится вне блока.A lambda expression cannot contain a goto statement, break statement, or continue statement that is inside the lambda function if the jump statement’s target is outside the block. Если целевой объект находится внутри блока, то наличие оператора перехода за пределами лямбда-функции также будет ошибкой.It is also an error to have a jump statement outside the lambda function block if the target is inside the block.

Спецификация языка C#C# Language Specification

Дополнительные сведения см. в спецификации языка C#.For more information, see the C# Language Specification. Спецификация языка является предписывающим источником информации о синтаксисе и использовании языка C#.The language specification is the definitive source for C# syntax and usage.

Делегаты, события и лямбда-выражения в справочном руководстве по C# 3.0, третье издание: более 250 решений для программистов на C# 3.0Delegates, Events, and Lambda Expressions in C# 3.0 Cookbook, Third Edition: More than 250 solutions for C# 3.0 programmers

См. такжеSee Also