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<TSource>。
當您使用以方法為基礎的語法呼叫 Where<TSource> 類別中的 Enumerable 方法時 (就像是在 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) 和可由編譯器隱含轉換為 int 類型的傳回值,因此 Lambda 運算式可以轉換為該類型的委派 (類型推斷將於下列各節中詳細討論)。 使用輸入參數 5 叫用委派時,它傳回的結果會是 25。
Lambda 不可出現在 is 或 as 運算子的左邊。
所有適用於匿名方法的限制,也都適用於 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 的主體可以包含方法呼叫。 不過,如果您要建立在 .NET Framework 外部 (例如在 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
--------------------------------------------------------------------------------
您可以使用 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 來新增相同的事件處理常式。 若要加入這個處理常式,請將 async 修飾詞加入至 Lambda 參數清單前面,如下列範例所示。
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 委派非常實用。 例如,請考慮下列委派類型:
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> 時,於例如 System.Linq.Queryable 中定義的標準查詢運算子內提供 Lambda 運算式。 當您指定 Expression<Func> 引數時,Lambda 將會編譯為運算式樹狀架構。
標準查詢運算子,也就是 Count<TSource> 方法,如下所示:
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
int oddNumbers = numbers.Count(n => n % 2 == 1);
編譯器會推斷輸入參數的類型,您也可以明確指定類型。 這個特殊 Lambda 運算式會計算除以二之後餘數為 1 的整數 (n)。
下列程式碼行產生的序列會包含 numbers 陣列中所有位於 9 左邊的元素,因為 9 是序列中第一個不符合條件的數字:
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 可以參考外部變數(Outer Variable) (請參閱匿名方法 (C# 程式設計手冊)),這些變數必須包含在定義 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 運算式中的 return 陳述式不會令其外圍(enclosing)方法返回。
•如果跳躍陳述式的目標不在區塊內,則 Lambda 運算式不可包含 Lambda 函式內的 goto 陳述式、break 陳述式或 continue 陳述式。 即使目標位於區塊內,跳躍陳述式出現在 Lambda 函式區塊外部也一樣是錯誤。
C# 語言規格
--------------------------------------------------------------------------------
如需詳細資訊,請參閱C# 語言規格。 語言規格是 C# 語法及用法的限定來源。
留言列表