Lambda 運算式 (C# 程式設計手冊)
Lambda 運算式是可用來建立委派或運算式樹狀架構型別的匿名函式。使用 Lambda 運算式,您可以撰寫可當做引數或傳回做為函式呼叫之值的區域函式。Lambda 運算式為撰寫 LINQ 查詢運算式中特別有用。
若要建立 Lambda 運算式,您在 Lambda 運算子 =>指定輸入參數 (如果有的話),因此,您在另一端將運算式或陳述式區塊放。例如, Lambda 運算式 x => x * x 指定名為 x 的參數和傳回值的 x 平方了。如下列範例所示,您可以指派運算式給委派型別:
delegate int del(int i);
static void Main(string[] args)
{
del myDelegate = x => x * x;
int j = myDelegate(5); //j = 25
}
若要建立運算式樹狀架構型別:
using System.Linq.Expressions;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Expression<del> myET = x => x * x;
}
}
}
=> 運算子具有與指派運算子 (=) 相同的優先順序,而且是右向關聯的。
在方法架構 LINQ 查詢中,Lambda 會用來做為標準查詢運算子方法的引數,例如 Where。
當您使用方法架構語法呼叫 Enumerable 類別中的 Where 方法時 (就像是在 LINQ to Objects 和 LINQ to XML 中),此參數就會是委派型別 System.Func<T, TResult>。Lambda 運算式是建立委派的最便利方式。例如,當您在 System.Linq.Queryable 類別中呼叫相同方法時 (就像是在 LINQ to SQL 中的方式),參數型別就會是 System.Linq.Expressions.Expression<Func>,其中 Func 是具有多達十六個輸入參數的任何 Func 委派。此外,Lambda 運算式只是建構該運算式樹狀架構的極致簡潔方式。Lambda 會使得 Where 呼叫看起來相似,但是實際上從 Lambda 建立的物件型別並不相同。
在上一個範例中,請注意委派簽章具有一個型別為 int 的隱含型別輸入參數,而且會傳回 int。因為 Lambda 運算式也有一個輸入參數 (x),以及可由編譯器 (Compiler) 隱含轉換為 int 型別的傳回值,所以 Lambda 運算式可以轉換為該型別的委派 (型別推斷將於下列各節中詳細討論)。當使用輸入參數 5 叫用 (Invoke) 委派時,便會傳回 25 的結果。
所有適用於匿名方法的限制,也都適用於 Lambda 運算式。如需詳細資訊,請參閱匿名方法 (C# 程式設計手冊)。
運算式 Lambda
左邊具有運算式的 Lambda 運算式稱為「運算式 Lambda」(Expression Lambda)。運算式 Lambda 會在運算式樹狀架構 (C# 和 Visual Basic)的建構過程中大量使用。運算式 Lambda 會傳回運算式的結果並採用下列基本形式:
(input parameters) => expression
當 Lambda 具有一個輸入參數時,括號才會是選擇性的,否則括號會是必要項目。兩個或更多個輸入參數則會由包括在括號中的逗號分隔:
(x, y) => x == y
有時候編譯器會很難或是無法推斷輸入型別。當這種情形發生時,您就可以明確指定型別,如下列範例所示:
(int x, string s) => s.Length > x
以空括號指定零個輸入參數:
() => SomeMethod()
請注意,在上述範例中,運算式 Lambda 的主體可以包含一個方法呼叫。然而,如果要建立將在另一個網域中 (例如 SQL Server) 使用的運算式樹狀架構,您就不應該在 Lambda 運算式中使用方法呼叫。這些方法在 .NET Common Language Runtime 內容的外部將不具任何意義。
陳述式 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 就像匿名方法,它不能用來建立運算式樹狀架構。
非同步 Lambdas
您可以輕鬆地建立使用 async 和 await 關鍵字,合併非同步處理中的 Lambda 運算式和陳述式。例如,下列範例包含 Windows Form 呼叫並等候非同步方法的事件處理常式, 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.\r\n";
}
async Task ExampleMethodAsync()
{
// The following line simulates a task-returning asynchronous process.
await Task.Delay(1000);
}
}
經由非同步 Lambda,您可以將相同的事件處理常式。若要加入處理常式,請在 Lambda 參數清單之前將 async 修飾詞,,如下列範例所示。
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
button1.Click += async (sender, e) =>
{
// ExampleMethodAsync returns a Task.
await ExampleMethodAsync();
textBox1.Text += "\r\nControl returned to Click event handler.\r\n";
};
}
async Task ExampleMethodAsync()
{
// The following line simulates a task-returning asynchronous process.
await Task.Delay(1000);
}
}
如需如何建立和使用非同步方法的詳細資訊,請參閱 使用 Async 和 Await 設計非同步程式 (C# 和 Visual Basic)。
具有標準查詢運算子的 Lambda
許多標準查詢運算子,都具有型別為一種 Func<T, TResult> 系列泛型委派的輸入參數。Func<T, TResult> 委派使用型別參數來定義輸入參數的數目和型別,以及委派的傳回型別。對於封裝套用至一組來源資料中每個項目的使用者定義運算式,Func 委派是非常有用的。例如,以下列委派型別為例:
public delegate TResult Func<TArg0, TResult>(TArg0 arg0)
這個委派可以具現化 (Instantiated) 為 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 將會編譯為運算式樹狀架構。
以下顯示一個標準查詢運算子,即 Count 方法:
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
int oddNumbers = numbers.Count(n => n % 2 == 1);
編譯器會推斷輸入參數的型別,或者您也可以明確予以指定。這個特定的 Lambda 運算式會計算這些在除以二時會產生餘數 1 的整數 (n)。
下列方法會使的 numbers 陣列中所有元素是左邊的一組數的序列,因為那是不符合條件的第一個數字:
var firstNumbersLessThan6 = numbers.TakeWhile(n => n < 6);
這個範例會示範如何用括號括住以指定多個輸入參數。此方法會傳回數字陣列中的所有元素,直到遇到數值小於其位置的數字為止。請勿混淆 Lambda 運算子 (=>) 與大於或等於運算子 (>=)。
var firstSmallNumbers = numbers.TakeWhile((n, index) => n >= index);
Lambda 中的型別推斷
當撰寫 Lambda 時,您通常不需要指定輸入參數的型別,這是因為編譯器可以根據 Lambda 主體、基礎委派型別,以及 C# 語言規格所說明的其他因素來推斷型別。對於大多數的標準查詢運算子而言,第一項輸入是來源序列中項目的型別。因此,如果您將要查詢 IEnumerable<Customer>,則輸入變數就會推斷為 Customer 物件,這表示您可以存取其方法和屬性:
customers.Where(c => c.City == "London");
以下是 Lambda 的一般規則:
Lambda 必須包含與委派型別相同數目的參數。
Lambda 中的每個輸入參數都必須能夠隱含轉換為其對應的委派參數。
Lambda 的傳回值 (如果存在) 必須能夠隱含轉換為委派的傳回型別。
請注意,Lambda 運算式本身並沒有型別,這是因為通用的型別系統沒有「Lambda 運算式」的內建概念。然而,非正式地說出 Lambda 運算式的「型別」,有時是很方便的功能。在這些情況下,該型別所指的會是委派型別,或是 Lambda 運算式所轉換成為的 Expression 型別。
Lambda 運算式中的變數範圍
Lambda 可以參考到封入方法或封入型別 (其中定義該 Lambda) 中範圍的「外部變數」(Outer Variable)。以這種方式擷取的變數會加以儲存以便在 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 運算式中的 return 陳述式不會使封入方法傳回。
Lambda 運算式不能包含 goto 陳述式、break 陳述式或 continue 陳述式,這些陳述式的目標位在主體外部,或是在所包含之匿名函式的主體中。
C# 語言規格
如需詳細資訊,請參閱 C# 語言規格。語言規格是 C# 語法和用法的限定來源。
精選書籍章節
C# 3.0 Cookbook, Third Edition: More than 250 solutions for C# 3.0 programmers 中的 Delegates, Events, and Lambda Expressions