“Lambda 運算式”是一個匿名函數,它可以包含運算式和語句,並且可用於建立委託或運算式分類樹類型。
所有 Lambda 運算式都使用 Lambda 運算子,該運算子讀為“goes to”。該 Lambda 運算子的左邊是輸入參數(如果有),右邊包含運算式或語句塊。Lambda 運算式 x => x * x 讀作“x goes to x times x”。可以將此運算式分配給委託類型,如下所示:
delegate int del(int i);del myDelegate = x => x * x;int j = myDelegate(5); //j = 25 |
建立運算式分類樹類型:
using System.Linq.Expressions;// ...Expression<del> = x => x * x; |
=> 運算子具有與賦值運算子 (=) 相同的優先順序,並且是右結合運算子。
Lambda 用在基於方法的 LINQ 查詢中,作為諸如 和 Where(IQueryable, String, array<Object>[]()[]) 等標準查詢運算子方法的參數。
使用基於方法的文法在 類中調用 方法時(像在 LINQ to Objects 和 LINQ to XML 中那樣),參數是委託類型。使用 Lambda 運算式建立委託最為方便。舉例來說,當您在 類中調用相同的方法時(像在 LINQ to SQL 中那樣),則參數類型是<Func>,其中 Func 是包含至多五個輸入參數的任何 Func 委託。同樣,Lambda 運算式只是一種用於構造運算式分類樹的非常簡練的方式。儘管事實上通過 Lambda 建立的對象的類型是不同的,但 Lambda 使得 Where 調用看起來類似。
在前面的樣本中,請注意委託簽名具有一個 int 類型的隱式類型輸入參數,並返回 int。可以將 Lambda 運算式轉換為該類型的委託,因為該運算式也具有一個輸入參數 (x),以及一個編譯器可隱式轉換為 int 類型的傳回值。(以下幾節中將對型別推斷進行詳細討論。) 使用輸入參數 5 調用委託時,它將返回結果 25。
在 或 運算子的左側不允許使用 Lambda。
適用於匿名方法的所有限制也適用於 Lambda 運算式。有關更多資訊,請參見匿名方法(C# 編程指南)。
Lambda 運算式
運算式在右邊的 Lambda 運算式稱為“Lambda 運算式”。 Lambda 運算式在構造時廣泛使用。Lambda 運算式返回運算式的結果,並採用以下基本形式:
(input parameters) => expression |
只有在 Lambda 有一個輸入參數時,括弧才是可選的;否則括弧是必需的。兩個或更多輸入參數由括在括弧中的逗號分隔:
有時,編譯器難於或無法推斷輸入類型。如果出現這種情況,您可以按以下樣本中所示方式顯式指定類型:
(int x, string s) => s.Length > x |
使用空括弧指定零個輸入參數:
在上一個樣本中,請注意 Lambda 運算式的主體可以包含方法調用。但是,如果要建立將在另一個域(比如 SQL Server)中使用的運算式分類樹,則不應在 Lambda 運算式中使用方法調用。方法在 .NET 公用語言運行庫內容相關的外部將沒有意義。
Lambda 語句
Lambda 語句與 Lambda 運算式類似,只是語句括在大括弧中:
(input parameters) => {statement;} |
Lambda 語句的主體可以包含任意數量的語句;但是,實際上通常不會多於兩個或三個語句。
delegate void TestDelegate(string s);…TestDelegate myDel = n => { string s = n + " " + "World"; Console.WriteLine(s); };myDel("Hello"); |
像匿名方法一樣,Lambda 語句無法用於建立運算式分類樹。
帶有標準查詢運算子的 Lambda
許多標準查詢運算子都具有輸入參數,其類型是泛型委派的 系列的其中之一。 委託使用型別參數來定義輸入參數的數量和類型,以及委託的傳回型別。Func 委託對於封裝應用於一組來源資料中每個元素的使用者定義運算式非常有用。例如,假設有以下委託類型:
public delegate TResult Func<TArg0, TResult>(TArg0 arg0) |
可以將委託執行個體化為 Func<int,bool> myFunc,其中 int 是輸入參數,bool 是傳回值。傳回值始終在最後一個型別參數中指定。Func<int, string, bool> 定義包含兩個輸入參數(int 和 string)且傳回型別為 bool 的委託。在調用下面的 Func 委託時,該委託將返回 true 或 false 以指示輸入參數是否等於 5:
Func<int, bool> myFunc = x => x == 5; bool result = myFunc(4); // returns false of course |
當參數類型為 Expression<Func> 時,您也可以提供 Lambda 運算式,例如在 System.Linq.Queryable 內定義的標準查詢運算子中。如果指定 Expression<Func> 參數,Lambda 將編譯為運算式分類樹。
此處顯示了一個標準查詢運算子, 方法:
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; int oddNumbers = numbers.Count(n => n % 2 == 1); |
編譯器可以推斷輸入參數的類型,或者您也可以顯式指定該類型。這個特別的 Lambda 運算式將計算整數 (n) 的數量,這些整數除以 2 時餘數為 1。
以下方法將產生一個序列,其中包含數字數組中出現在“9”之前的所有元素,因為“9”是序列中不滿足條件的第一個數字:
var firstNumbersLessThan6 = numbers.TakeWhile(n => n < 6); |
此樣本示範如何通過將輸入參數括在括弧中來指定多個輸入參數。該方法將返回數字數組中的所有元素,直至遇到一個值小於其位置的數字為止。不要將 Lambda 運算子 (=>) 與大於等於運算子 (>=) 混淆。
var firstSmallNumbers = numbers.TakeWhile((n, index) => n >= index); |
Lambda 中的型別推斷
在編寫 Lambda 時,通常不必為輸入參數指定類型,因為編譯器可以基於 Lambda 主體、基礎委託類型以及 C# 3.0 語言規範中描述的其他因素推斷類型。對於大多數標準查詢運算子,第一個輸入是源序列中的元素的類型。因此,如果要查詢 IEnumerable<Customer>,則輸入變數將被推斷為 Customer 對象,這意味著您可以訪問其方法和屬性:
customers.Where(c => c.City == "London"); |
Lambda 的一般規則如下:
Lambda 包含的參數數量必須與委託類型包含的參數數量相同。
Lambda 中的每個輸入參數必須都能夠隱式轉換為其對應的委託參數。
Lambda 的傳回值(如果有)必須能夠隱式轉換為委託的傳回型別。
請注意,Lambda 運算式本身沒有類型,因為一般型別系統沒有“Lambda 運算式”這一內部概念。但是,有時會不正式地論及 Lambda 運算式的“類型”。在這些情況下,類型是指委託類型或 Lambda 運算式所轉換為的 類型。
Lambda 運算式中的變數範圍
Lambda 可以引用“外部變數”,這些變數位於在其中定義 Lambda 的封閉方法或類型的範圍內。將會儲存通過這種方法捕獲的變數以供在 Lambda 運算式中使用,即使變數將以其他方式超出範圍或被作為記憶體回收。必須明確地分配外部變數,然後才能在 Lambda 運算式中使用該變數。下面的樣本示範這些規則:
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(); } } |
下列規則適用於 Lambda 運算式中的變數範圍:
捕獲的變數將不會被作為記憶體回收,直至引用變數的委託超出範圍為止。
在外部方法中看不到 Lambda 運算式內引入的變數。
Lambda 運算式無法從封閉方法中直接捕獲 ref 或 out 參數。
Lambda 運算式中的返回語句不會導致封閉方法返回。
Lambda 運算式不能包含其目標位於所包含匿名函數主體外部或內部的 goto 語句、break 語句或 continue 語句。
C# 語言規範
有關更多資訊,請參見 C# 語言規範中的以下各章節: