目錄
寫在前面
上篇文章介紹了Lambda的基本概念以及匿名方法,本篇繼續介紹Lambda的一些內容,既然學了,就要總結的全面一點。
系列文章
帶有標準查詢運算子的Lambda
什麼事標準查詢運算子?
“標準查詢運算子”是組成語言整合查詢 (LINQ) 模式的方法。 大多數這些方法都在序列上執行,其中的序列是一個物件,其型別實現了 IEnumerable<T> 介面或 IQueryable<T> 介面。 標準查詢運算子提供了包括篩選、投影、聚合、排序等功能在內的查詢功能。
共有兩組 LINQ 標準查詢運算子,一組在型別為 IEnumerable<T> 的物件上執行,另一組在型別為 IQueryable<T> 的物件上執行。 構成每組運算子的方法分別是 Enumerable 和 Queryable 類的靜態成員。 這些方法被定義為作為方法執行目標的型別的“擴充套件方法”。 這意味著可以使用靜態方法語法或例項方法語法來呼叫它們。
此外,許多標準查詢運算子方法執行所針對的型別不是基於 IEnumerable<T> 或 IQueryable<T> 的型別。 Enumerable 型別定義兩個此類方法,這些方法都在型別為 IEnumerable 的物件上執行。 利用這些方法(Cast<TResult>(IEnumerable) 和 OfType<TResult>(IEnumerable)),您將能夠在 LINQ 模式中查詢非引數化或非泛型集合。 這些方法通過建立一個強型別的物件集合來實現這一點。 Queryable 類定義兩個類似的方法(Cast<TResult>(IQueryable) 和 OfType<TResult>(IQueryable)),這些方法在型別為 Queryable 的物件上執行。
各個標準查詢運算子在執行時間上有所不同,具體情況取決於它們是返回單一值還是值序列。 返回單一值的方法(例如 Average 和 Sum)會立即執行。 返回序列的方法會延遲查詢執行,並返回一個可列舉的物件。
對於在記憶體中集合上執行的方法(即擴充套件 IEnumerable<T> 的那些方法),返回的可列舉物件將捕獲傳遞到方法的引數。 在列舉該物件時,將使用查詢運算子的邏輯,並返回查詢結果。
與之相反,擴充套件 IQueryable<T> 的方法不會實現任何查詢行為,但會生成一個表示要執行的查詢的表示式樹。 查詢處理由源 IQueryable<T> 物件處理。
可以在一個查詢中將對查詢方法的呼叫連結在一起,這就使得查詢的複雜性可能會變得不確定。來自:http://msdn.microsoft.com/zh-cn/library/bb397896.aspx
多數標準查詢運算子都有輸入引數,其型別是17個.net泛型委託Func<T,TResult>中的一種。
比如:
1 // 2 // 摘要: 3 // 返回一個數字,表示在指定的序列中滿足條件的元素數量。 4 // 5 // 引數: 6 // source: 7 // 包含要測試和計數的元素的序列。 8 // 9 // predicate: 10 // 用於測試每個元素是否滿足條件的函式。 11 // 12 // 型別引數: 13 // TSource: 14 // source 中的元素的型別。 15 // 16 // 返回結果: 17 // 一個數字,表示序列中滿足謂詞函式條件的元素數量。 18 // 19 // 異常: 20 // System.ArgumentNullException: 21 // source 或 predicate 為 null。 22 // 23 // System.OverflowException: 24 // source 中的元素數量大於 System.Int32.MaxValue。 25 public static int Count<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
從上面的程式碼中可以看出Count方法擴充套件自IEnumerable<TSource>,並且有輸入引數Func<TSource, bool> predicate。
一個例子
1.判斷輸入引數是否大於5,呼叫myFunc(4),如果大於5返回true,否則返回false。
2.輸入一個int型別的整數,並輸入一個string型別的字串,判斷拼接後的字串是否為空,並返回bool型別的結果。
1 //其中 int 是輸入引數,bool 是返回值 返回值始終在最後一個型別引數中指定 2 Func<int, bool> myFunc = x => x > 5; 3 bool result = myFunc(4); 4 //其中 int string是輸入引數,bool 是返回值 返回值始終在最後一個型別引數中指定 5 //上篇文章中已經指出,在有多個輸入引數的情況下,必須使用括號,輸入引數以逗號隔開 6 Func<int, string, bool> myFunc2 = (x, y) => string.IsNullOrEmpty(x.ToString() + y);
當引數型別為 Expression<Func> 時,也可以提供 Lambda 表示式,例如在 System.Linq.Queryable 內定義的標準查詢運算子中, 如果指定 Expression<Func> 引數,lambda 將編譯為表示式目錄樹。
一個例子
此處列舉一個標準查詢運算子,Count 方法:
1 //一個整數陣列 2 int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; 3 //計算整數陣列中奇數的個數 4 int oddNumbers = numbers.Count(n => n % 2 == 1); 5 Console.WriteLine(oddNumbers);
在這裡你會發現,n直接使用n%2,編譯器將推斷n的型別為整型。
從上面的定義及例子,發現標準查詢運算子,有這樣的特點:1,查詢什麼(集合或者陣列,集合和陣列有什麼特點?要麼實現了IEnumerable<T> 介面,要麼IQueryable<T> 介面)。
Lambda中型別推斷
在編寫 lambda 時,通常不必為輸入引數指定型別,因為編譯器可以根據 lambda 主體、引數的委託型別以及 C# 語言規範中描述的其他因素來推斷型別。 對於大多數標準查詢運算子,第一個輸入是源序列中的元素型別。 因此,如果要查詢 IEnumerable<Customer>,則輸入變數將被推斷為 Customer 物件,這意味著你可以訪問其方法和屬性:
1 customers.Where(c => c.City == "London");
Lambda的一般規則:
Lambda 包含的引數數量必須與委託型別包含的引數數量相同。
Lambda 中的每個輸入引數必須都能夠隱式轉換為其對應的委託引數。
Lambda 的返回值(如果有)必須能夠隱式轉換為委託的返回型別。
Lambda表示式中變數作用域
lambda表示式中變數的作用域在定義 lambda 函式的方法內或包含 lambda 表示式的型別內,lambda 可以引用範圍內的外部變數。 以這種方式捕獲的變數將進行儲存以備在 lambda 表示式中使用,即使在其他情況下,這些變數將超出範圍並進行垃圾回收。 必須明確地分配外部變數,然後才能在 lambda 表示式中使用該變數。
一個例子
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading; 6 using System.Threading.Tasks; 7 8 namespace Wolfy.LinqDemo 9 { 10 delegate bool D(); 11 delegate bool D2(int i); 12 13 class Program 14 { 15 static D del; 16 static D2 del2; 17 static void Main(string[] args) 18 { 19 //呼叫TestMethod方法 20 TestMethod(5); 21 // Prove that del2 still has a copy of 22 // local variable j from TestMethod. 23 //證明del2仍保留方法TestMethod中變數j的副本。 24 bool result = del2(10); 25 //輸出true 26 Console.WriteLine(result); 27 Console.ReadKey(); 28 } 29 static void TestMethod(int input) 30 { 31 int j = 0; 32 // 使用lambda表示式初始化del. 33 // Note access to 2 outer variables. 34 // del will be invoked within this method(del將在這個方法內部執行). 35 del = () => { j = 10; return j > input; }; 36 // del2 will be invoked after TestMethod goes out of scope. 37 //del2將在TestMethod外部執行。 38 del2 = (x) => { return x == j; }; 39 // Demonstrate value of j: 40 //展示j的值 41 // Output: j = 0 42 //輸出j=0 43 // The delegate has not been invoked yet. 44 //委託仍沒有執行 45 Console.WriteLine("j = {0}", j); 46 // Invoke the delegate. 47 //委託執行 48 bool boolResult = del(); 49 // Output: j = 10 b = True 50 //輸出j=10 b=True 51 Console.WriteLine("j = {0}. b = {1}", j, boolResult); 52 } 53 54 } 55 }
在執行TestMethod中的以下程式碼時:
1 // Invoke the delegate. 2 //委託執行 3 bool boolResult = del(); 4 // Output: j = 10 b = True 5 //輸出j=10 b=True 6 Console.WriteLine("j = {0}. b = {1}", j, boolResult);
呼叫del()修改了j的值為10,在Main方法中呼叫del2(10),此時仍保留方法TestMehod中j的副本。所以此時輸出為:
適用於 lambda 表示式中的變數範圍的規則
捕獲的變數將不會被作為垃圾回收,直至引用變數的委託符合垃圾回收的條件。
在外部方法中看不到 lambda 表示式內引入的變數。
Lambda 表示式無法從封閉方法中直接捕獲 ref 或 out 引數。
Lambda 表示式中的返回語句不會導致封閉方法返回。
如果跳轉語句的目標在塊外部,則 lambda 表示式不能包含位於 lambda 函式內部的 goto 語句、break 語句或 continue 語句。 同樣,如果目標在塊內部,則在 lambda 函式塊外部使用跳轉語句也是錯誤的。
非同步Lambda
通過使用 async 和 await 關鍵字,你可以輕鬆建立包含非同步處理的 lambda 表示式和語句。
一個例子
在winform的單擊事件,非同步的方式呼叫方法ExampleMethodAsync。
1 private async void button1_Click(object sender, EventArgs e) 2 { 3 await ExampleMethodAsync(); 4 } 5 async Task ExampleMethodAsync() 6 { 7 // 下面模擬一個任務返回的非同步程式。 8 await Task.Delay(1000); 9 }
如果使用非同步Lambda,你可以這樣寫,注意在lambda前加上async關鍵字。
1 button1.Click += async (sender, e) => 2 { 3 await ExampleMethodAsync(); 4 }; 5 async Task ExampleMethodAsync() 6 { 7 await Task.Delay(1000); 8 }
看上去更簡潔。
總結
本篇文章介紹了lambda標準查詢運算子,變數範圍,型別推斷,非同步等概念。其他的還好理解,唯有變數範圍太繞了,也只有做個記錄慢慢體會了。
參考文章
http://msdn.microsoft.com/zh-cn/library/bb397896.aspx
http://msdn.microsoft.com/zh-cn/library/bb397687.aspx