Lambda 表达式(C# 编程指南)Lambda expressions (C# Programming Guide)

“Lambda 表达式”是采用以下任意一种形式的表达式: A lambda expression is an expression of any of the following two forms:

使用 lambda 声明运算符=> 从其主体中分离 lambda 参数列表。Use the lambda declaration operator => to separate the lambda's parameter list from its body. 若要创建 Lambda 表达式,需要在 Lambda 运算符左侧指定输入参数(如果有),然后在另一侧输入表达式或语句块。To create a lambda expression, you specify input parameters (if any) on the left side of the lambda operator and an expression or a statement block on the other side.

任何 Lambda 表达式都可以转换为委托类型。Any lambda expression can be converted to a delegate type. Lambda 表达式可以转换的委托类型由其参数和返回值的类型定义。The delegate type to which a lambda expression can be converted is defined by the types of its parameters and return value. 如果 lambda 表达式不返回值,则可以将其转换为 Action 委托类型之一;否则,可将其转换为 Func 委托类型之一。If a lambda expression doesn't return a value, it can be converted to one of the Action delegate types; otherwise, it can be converted to one of the Func delegate types. 例如,有 2 个参数且不返回值的 Lambda 表达式可转换为 Action<T1,T2> 委托。For example, a lambda expression that has two parameters and returns no value can be converted to an Action<T1,T2> delegate. 有 1 个参数且不返回值的 Lambda 表达式可转换为 Func<T,TResult> 委托。A lambda expression that has one parameter and returns a value can be converted to a Func<T,TResult> delegate. 以下示例中,lambda 表达式 x => x * x(指定名为 x 的参数并返回 x 平方值)将分配给委托类型的变量:In the following example, the lambda expression x => x * x, which specifies a parameter that’s named x and returns the value of x squared, is assigned to a variable of a delegate type:

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

表达式 lambda 还可以转换为表达式树类型,如下面的示例所示:Expression lambdas also can be converted to the expression tree types, as the following example shows:

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

可在需要委托类型或表达式树的实例的任何代码中使用 lambda 表达式,例如,作为 Task.Run(Action) 方法的参数传递应在后台执行的代码。You can use lambda expressions in any code that requires instances of delegate types or expression trees, for example as an argument to the Task.Run(Action) method to pass the code that should be executed in the background. 编写 LINQ 查询表达式时,还可以使用 lambda 表达式,如下例所示:You also can use lambda expressions when you write LINQ query expressions, as the following example shows:

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

如果使用基于方法的语法在 System.Linq.Enumerable 类中(例如,在 LINQ to Objects 和 LINQ to XML 中)调用 Enumerable.Select 方法,则参数为委托类型 System.Func<T,TResult>When you use method-based syntax to call the Enumerable.Select method in the System.Linq.Enumerable class, for example in LINQ to Objects and LINQ to XML, the parameter is a delegate type System.Func<T,TResult>. 如果在 System.Linq.Queryable 类中(例如,在 LINQ to SQL 中)调用 Queryable.Select 方法,则参数类型为表达式树类型 Expression<Func<TSource,TResult>>When you call the Queryable.Select method in the System.Linq.Queryable class, for example in LINQ to SQL, the parameter type is an expression tree type Expression<Func<TSource,TResult>>. 在这两种情况下,都可以使用相同的 lambda 表达式来指定参数值。In both cases you can use the same lambda expression to specify the parameter value. 尽管通过 Lambda 创建的对象实际具有不同的类型,但其使得 2 个 Select 调用看起来类似。That makes the two Select calls to look similar although in fact the type of objects created from the lambdas is different.

表达式 lambdaExpression lambdas

表达式位于 => 运算符右侧的 lambda 表达式称为“表达式 lambda” 。A lambda expression with an expression on the right side of the => operator is called an expression lambda. 表达式 lambda 广泛用于表达式树的构造。Expression lambdas are used extensively in the construction of expression trees. 表达式 lambda 会返回表达式的结果,并采用以下基本形式:An expression lambda returns the result of the expression and takes the following basic form:

(input-parameters) => expression

仅当 lambda 只有一个输入参数时,括号才是可选的;否则括号是必需的。The parentheses are optional only if the lambda has one input parameter; otherwise they are required.

使用空括号指定零个输入参数:Specify zero input parameters with empty parentheses:

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

括号内的两个或更多输入参数使用逗号加以分隔:Two or more input parameters are separated by commas enclosed in parentheses:

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

有时,编译器无法推断输入类型。Sometimes it's impossible for the compiler to infer the input types. 可以显式指定类型,如下面的示例所示:You can specify the types explicitly as shown in the following example:

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

输入参数类型必须全部为显式或全部为隐式;否则,便会生成 CS0748 编译器错误。Input parameter types must be all explicit or all implicit; otherwise, a CS0748 compiler error occurs.

表达式 lambda 的主体可以包含方法调用。The body of an expression lambda can consist of a method call. 不过,若要创建在 .NET 公共语言运行时的上下文之外(如在 SQL Server 中)计算的表达式树,不得在 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. 在 .NET 公共语言运行时上下文之外,方法将没有任何意义。The methods will have no meaning outside the context of the .NET common language runtime.

语句 lambdaStatement lambdas

语句 lambda 与表达式 lambda 表达式类似,只是语句括在大括号中:A statement lambda resembles an expression lambda except that the statement(s) is enclosed in braces:

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

语句 lambda 的主体可以包含任意数量的语句;但是,实际上通常不会多于两个或三个。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!

语句 lambda 也不能用于创建表达式目录树。Statement lambdas cannot be used to create expression trees.

异步 lambdaAsync lambdas

通过使用 asyncawait 关键字,你可以轻松创建包含异步处理的 lambda 表达式和语句。You can easily create lambda expressions and statements that incorporate asynchronous processing by using the async and await keywords. 例如,下面的 Windows 窗体示例包含一个调用和等待异步方法 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();
        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);
    }
}

你可以使用异步 lambda 添加同一事件处理程序。You can add the same event handler by using an async lambda. 若要添加此处理程序,请在 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) =>
        {
            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);
    }
}

有关如何创建和使用异步方法的详细信息,请参阅使用 Async 和 Await 的异步编程For more information about how to create and use async methods, see Asynchronous Programming with async and await.

lambda 表达式和元组Lambda expressions and tuples

自 C# 7.0 起,C# 语言提供对元组的内置支持。Starting with C# 7.0, the C# language provides built-in support for tuples. 可以提供一个元组作为 Lambda 表达式的参数,同时 Lambda 表达式也可以返回元组。You can provide a tuple as an argument to a lambda expression, and your lambda expression can also return a tuple. 在某些情况下,C# 编译器使用类型推理来确定元组组件的类型。In some cases, the C# compiler uses type inference to determine the types of tuple components.

可通过用括号括住用逗号分隔的组件列表来定义元组。You define a tuple by enclosing a comma-delimited list of its components in parentheses. 下面的示例使用包含三个组件的元组,将一系列数字传递给 lambda 表达式,此表达式将每个值翻倍,然后返回包含乘法运算结果的元组(内含三个组件)。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)

通常,元组字段命名为 Item1Item2 等等。但是,可以使用命名组件定义元组,如以下示例所示。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}");

若要详细了解 C# 元组,请参阅 C# 元组类型For more information about C# tuples, see C# tuple types.

含标准查询运算符的 lambdaLambdas with the standard query operators

在其他实现中,LINQ to Objects 有一个输入参数,其类型是泛型委托 Func<TResult> 系列中的一种。LINQ to Objects, among other implementations, have an input parameter whose type is one of the Func<TResult> family of generic delegates. 这些委托使用类型参数来定义输入参数的数量和类型,以及委托的返回类型。These delegates use type parameters to define the number and type 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. 例如,假设为 Func<T,TResult> 委托类型:For example, consider the Func<T,TResult> delegate type:

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

可以将委托实例化为 Func<int, bool> 实例,其中 int 是输入参数,bool 是返回值。The delegate can be instantiated as a Func<int, bool> instance 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> 定义包含两个输入参数(intstring)且返回类型为 bool的委托。For example, Func<int, string, bool> defines a delegate with two input parameters, int and string, and a return type of bool. 下面的 Func 委托在调用后返回布尔值,以指明输入参数是否等于 5: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

参数类型为 Expression<TDelegate> 时,也可以提供 Lambda 表达式,例如在 Queryable 类型内定义的标准查询运算符中提供。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. 指定 Expression<TDelegate> 参数时,lambda 编译为表达式树。When you specify an Expression<TDelegate> argument, the lambda is compiled to an expression tree.

下面的示例使用 Count 标准查询运算符: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)}");

编译器可以推断输入参数的类型,或者你也可以显式指定该类型。The compiler can infer the type of the input parameter, or you can also specify it explicitly. 这个特殊 lambda 表达式将计算那些除以 2 时余数为 1 的整数的数量 (n)。This particular lambda expression counts those integers (n) which when divided by two have a remainder of 1.

下面的示例生成一个序列,其中包含 numbers 数组中位于 9 之前的所有元素,因为这是序列中第一个不符合条件的数字: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

以下示例通过将输入参数括在括号中来指定多个输入参数。The following example specifies multiple input parameters by enclosing them in parentheses. 此方法返回 numbers 数组中的所有元素,直至遇到值小于其在数组中的序号位置的数字为止: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

Lambda 表达式中的类型推理Type inference in lambda expressions

编写 lambda 时,通常不必为输入参数指定类型,因为编译器可以根据 lambda 主体、参数类型以及 C# 语言规范中描述的其他因素来推断类型。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. 对于大多数标准查询运算符,第一个输入是源序列中的元素类型。For most of the standard query operators, the first input is the type of the elements in the source sequence. 如果要查询 IEnumerable<Customer>,则输入变量将被推断为 Customer 对象,这意味着你可以访问其方法和属性: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");

lambda 类型推理的一般规则如下:The general rules for type inference for lambdas are as follows:

  • Lambda 包含的参数数量必须与委托类型包含的参数数量相同。The lambda must contain the same number of parameters as the delegate type.

  • Lambda 中的每个输入参数必须都能够隐式转换为其对应的委托参数。Each input parameter in the lambda must be implicitly convertible to its corresponding delegate parameter.

  • Lambda 的返回值(如果有)必须能够隐式转换为委托的返回类型。The return value of the lambda (if any) must be implicitly convertible to the delegate's return type.

请注意,lambda 表达式本身没有类型,因为通用类型系统没有“lambda 表达式”这一固有概念。Note that lambda expressions in themselves don't have a type because the common type system has no intrinsic concept of "lambda expression." 不过,有时以一种非正式的方式谈论 lambda 表达式的“类型”会很方便。However, it's sometimes convenient to speak informally of the "type" of a lambda expression. 在这些情况下,类型是指委托类型或 lambda 表达式所转换到的 Expression 类型。In these cases the type refers to the delegate type or Expression type to which the lambda expression is converted.

捕获 lambda 表达式中的外部变量和变量范围Capture of outer variables and variable scope in lambda expressions

lambda 可以引用外部变量 。Lambdas can refer to outer variables. 这些变量是在定义 lambda 表达式的方法中或包含 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. 以这种方式捕获的变量将进行存储以备在 lambda 表达式中使用,即使在其他情况下,这些变量将超出范围并进行垃圾回收。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. 必须明确地分配外部变量,然后才能在 lambda 表达式中使用该变量。An outer variable must be definitely assigned before it can be consumed in a lambda expression. 下面的示例演示这些规则: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
}

下列规则适用于 lambda 表达式中的变量范围: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.

  • 在封闭方法中看不到 lambda 表达式内引入的变量。Variables introduced within a lambda expression are not visible in the enclosing method.

  • lambda 表达式无法从封闭方法中直接捕获 inrefout 参数。A lambda expression cannot directly capture an in, ref, or out parameter from the enclosing method.

  • lambda 表达式中的 return 语句不会导致封闭方法返回。A return statement in a lambda expression doesn't cause the enclosing method to return.

  • 如果相应跳转语句的目标位于 lambda 表达式块之外,lambda 表达式不得包含 gotobreakcontinue 语句。A lambda expression cannot contain a goto, break, or continue statement if the target of that jump statement is outside the lambda expression block. 同样,如果目标在块内部,在 lambda 表达式块外部使用跳转语句也是错误的。It's also an error to have a jump statement outside the lambda expression block if the target is inside the block.

C# 语言规范C# language specification

有关详细信息,请参阅 C# 语言规范中的 匿名函数表达式部分。For more information, see the Anonymous function expressions section of the C# language specification.

C# 3.0 手册(第三版):面向 C# 3.0 程序员的超过 250 个解决方案中的委托、事件和 Lambda 表达式Delegates, Events, and Lambda Expressions in C# 3.0 Cookbook, Third Edition: More than 250 solutions for C# 3.0 programmers

请参阅See also