Lambda 表達式是做爲對象處理的代碼塊(表達式或語句塊)。 它可做爲參數傳遞給方法,也可經過方法調用返回。 Lambda 表達式普遍用於:html
將要執行的代碼傳遞給異步方法,例如 Task.Run(Action)。express
編寫 LINQ 查詢表達式。編程
建立表達式樹。api
Lambda 表達式是能夠表示爲委託的代碼,或者表示爲表達式樹的代碼,它所表示的表達式樹能夠編譯爲委託。 Lambda 表達式的特定委託類型取決於其參數和返回值。 不返回值的 Lambda 表達式對應於 Action
委託,具體取決於其參數數量。 返回值的 Lambda 表達式對應於 Func
委託,具體取決於其參數數量。 例如,有 2 個參數但不返回值的 Lambda 表達式對應於 Action<T1,T2> 委託。 有 1 個參數並返回值的 Lambda 表達式對應於 Func<T,TResult> 委託。數組
Lambda 表達式使用 lambda 聲明運算符 =>
從其可執行代碼中分離 lambda 參數列表。 若要建立 Lambda 表達式,須要在 lambda 運算符左側指定輸入參數(若是有),而後在另外一側輸入表達式或語句塊。 例如,單行 Lambda 表達式 x => x * x
指定名爲 x
的參數並返回 x
的平方值。 以下面的示例所示,你能夠將此表達式分配給委託類型:異步
Func<int, int> square = x => x * x; Console.WriteLine(square(5)); // 輸出: // 25
還能夠將 lambda 表達式分配給表達式樹類型:async
System.Linq.Expressions.Expression<Func<int, int>> e = x => x * x; Console.WriteLine(e); // 輸出: // x => (x * x)
或者,能夠將其直接做爲方法參數傳遞:ide
int[] numbers = { 2, 3, 4, 5 }; var squaredNumbers = numbers.Select(x => x * x); Console.WriteLine(string.Join(" ", squaredNumbers)); // 輸出: // 4 9 16 25
若是使用基於方法的語法在 System.Linq.Enumerable 類中調用 Enumerable.Select 方法(就像在 LINQ to Objects 和 LINQ to XML 中同樣),參數是委託類型 System.Func<T,TResult>。 使用 Lambda 表達式建立該委託最爲方便。 若是在 System.Linq.Queryable 類中調用 Queryable.Select 方法(就像在 LINQ to SQL 中同樣),參數類型是表達式樹類型 Expression<Func<TSource,TResult>>
。 一樣,Lambda 表達式只是一種很是簡潔的構造該表達式目錄樹的方式。 儘管事實上經過 Lambda 建立的對象具備不一樣的類型,但 Lambda 使得 Select
調用看起來相似。異步編程
全部適用於匿名方法的限制也都適用於 lambda 表達式。函數
=>
運算符右側的 lambda 表達式稱爲「表達式 lambda」。 表達式 lambda 普遍用於表達式樹的構造。 表達式 lambda 會返回表達式的結果,並採用如下基本形式:
(input-parameters) => expression
僅當 lambda 只有一個輸入參數時,括號纔是可選的;不然括號是必需的。
使用空括號指定零個輸入參數:
Action line = () => Console.WriteLine();
括號內的兩個或更多輸入參數使用逗號加以分隔:
Func<int, int, bool> testForEquality = (x, y) => x == y;
有時,編譯器沒法推斷輸入類型。 能夠顯式指定類型,以下面的示例所示:
Func<int, string, bool> isTooLong = (int x, string s) => s.Length > x;
輸入參數類型必須所有爲顯式或所有爲隱式;不然,便會生成 CS0748 編譯器錯誤。
表達式 lambda 的主體能夠包含方法調用。 不過,若要建立在 .NET 公共語言運行時的上下文以外(如在 SQL Server 中)計算的表達式樹,不得在 lambda 表達式中使用方法調用。在 .NET 公共語言運行時上下文以外,方法將沒有任何意義。
(input-parameters) => { statement; }
Action<string> greet = name => { string greeting = $"Hello {name}!"; Console.WriteLine(greeting); }; greet("World"); // 輸出: // Hello World!
像匿名方法同樣,語句 lambda 也不能用於建立表達式目錄樹。
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() { // 模擬返回異步進程的任務 await Task.Delay(1000); } }
async
修飾符,以下面的示例所示:
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() { // 模擬返回異步進程的任務 await Task.Delay(1000); } }
有關如何建立和使用異步方法的詳細信息,請參閱使用 Async 和 Await 的異步編程。
自 C# 7.0 起,C# 語言提供對元組的內置支持。 能夠提供一個元組做爲 Lambda 表達式的參數,同時 Lambda 表達式也能夠返回元組。 在某些狀況下,C# 編譯器使用類型推理來肯定元組組件的類型。
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}"); // 輸出: // The set (2, 3, 4) doubled: (4, 6, 8)
可經過用括號括住用逗號分隔的組件列表來定義元組。 下面的示例使用包含三個組件的元組,將一系列數字傳遞給 lambda 表達式,此表達式將每一個值翻倍,而後返回包含乘法運算結果的元組(內含三個組件)。
一般,元組字段命名爲 Item1
、Item2
等等。可是,可使用命名組件定義元組,如如下示例所示。
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# 元組類型。
Func
委託對於封裝用戶定義的表達式很是有用,這些表達式將應用於一組源數據中的每一個元素。 例如,假設爲 Func<T,TResult> 委託類型:
public delegate TResult Func<in T, out TResult>(T arg)
能夠將委託實例化爲 Func<int, bool>
實例,其中 int
是輸入參數,bool
是返回值。 返回值始終在最後一個類型參數中指定。 例如,Func<int, string, bool>
定義包含兩個輸入參數(int
和 string
)且返回類型爲 bool
的委託。 下面的 Func
委託在調用後返回布爾值,以指明輸入參數是否等於 5:
Func<int, bool> equalsFive = x => x == 5; bool result = equalsFive(4); Console.WriteLine(result); // False
參數類型爲 Expression<TDelegate> 時,也能夠提供 Lambda 表達式,例如在 Queryable類型內定義的標準查詢運算符中提供。 指定 Expression<TDelegate> 參數時,lambda 編譯爲表達式樹。
下面的示例使用 Count 標準查詢運算符:
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)}");
編譯器能夠推斷輸入參數的類型,或者你也能夠顯式指定該類型。 這個特殊 lambda 表達式將計算那些除以 2 時餘數爲 1 的整數的數量 (n
)。
下面的示例生成一個序列,其中包含 numbers
數組中位於 9 以前的全部元素,由於這是序列中第一個不符合條件的數字:
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; var firstNumbersLessThanSix = numbers.TakeWhile(n => n < 6); Console.WriteLine(string.Join(" ", firstNumbersLessThanSix)); // 輸出: // 5 4 1 3
如下示例經過將輸入參數括在括號中來指定多個輸入參數。 此方法返回 numbers
數組中的全部元素,直至遇到值小於其在數組中的序號位置的數字爲止:
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)); // 輸出: // 5 4
IEnumerable<Customer>
,則輸入變量將被推斷爲 Customer
對象,這意味着你能夠訪問其方法和屬性:
customers.Where(c => c.City == "London");
lambda 類型推理的通常規則以下:
Lambda 包含的參數數量必須與委託類型包含的參數數量相同。
Lambda 中的每一個輸入參數必須都可以隱式轉換爲其對應的委託參數。
Lambda 的返回值(若是有)必須可以隱式轉換爲委託的返回類型。
請注意,lambda 表達式自己沒有類型,由於通用類型系統沒有「lambda 表達式」這一固有概念。 不過,有時以一種非正式的方式談論 lambda 表達式的「類型」會很方便。 在這些狀況下,類型是指委託類型或 lambda 表達式所轉換到的 Expression 類型。
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}"); } // 輸出: // 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 表達式中的變量範圍:
捕獲的變量將不會被做爲垃圾回收,直至引用變量的委託符合垃圾回收的條件。
在封閉方法中看不到 lambda 表達式內引入的變量。
lambda 表達式中的 return 語句不會致使封閉方法返回。
若是相應跳轉語句的目標位於 lambda 表達式塊以外,lambda 表達式不得包含 goto、break 或 continue 語句。 一樣,若是目標在塊內部,在 lambda 表達式塊外部使用跳轉語句也是錯誤的。
其餘技術請參考