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. 例如,具有兩個參數且不會傳回值的 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. 具有一個參數且會傳回值的 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 類別中的 Enumerable.Select 方法時 (例如在 LINQ to Objects 和 LINQ to XML中所執行),此參數會是委派型別 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 類別中呼叫 Queryable.Select 方法時 (例如在 LINQ to SQL 中所執行),參數型別是運算式樹狀架構型別 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. 那會讓兩個 Select 呼叫看起來很相似,但是實際上從 Lambda 建立的物件型別並不相同。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 Form 範例包含呼叫並等候非同步方法 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. 若要加入這個處理常式,請將 async 修飾詞加入至 Lambda 參數清單前面,如下列範例所示: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. 下列範例使用具有 3 個元件的元組將一連串數字傳遞至 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 運算式會計算除以二之後餘數為 1 的整數 (n)。This particular lambda expression counts those integers (n) which when divided by two have a remainder of 1.

下列範例產生的序列會包含 numbers 陣列中所有位於 9 前面的元素,因為 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 Cookbook 第三版:250 個以上 C# 3.0 程式設計人員適用的方案中的委派、事件與 Lambda 運算式Delegates, Events, and Lambda Expressions in C# 3.0 Cookbook, Third Edition: More than 250 solutions for C# 3.0 programmers

另請參閱See also