目錄
寫在前面
系列文章
帶有标準查詢運算符的Lambda
Lambda中類型推斷
Lambda表達式中變量作用域
異步Lambda
總結
上篇文章介紹了Lambda的基本概念以及匿名方法,本篇繼續介紹Lambda的一些内容,既然學了,就要總結的全面一點。
Linq之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 主體、參數的委托類型以及 C# 語言規範中描述的其他因素來推斷類型。 對于大多數标準查詢運算符,第一個輸入是源序列中的元素類型。 是以,如果要查詢 IEnumerable<Customer>,則輸入變量将被推斷為 Customer 對象,這意味着你可以通路其方法和屬性:
1 customers.Where(c => c.City == "London");
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 函數塊外部使用跳轉語句也是錯誤的。
通過使用 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
-
部落格位址:http://www.cnblogs.com/wolf-sun/
部落格版權:如果文中有不妥或者錯誤的地方還望高手的你指出,以免誤人子弟。如果覺得本文對你有所幫助不如【推薦】一下!如果你有更好的建議,不如留言一起讨論,共同進步!
再次感謝您耐心的讀完本篇文章。