Linq之Lambda進階


目錄

寫在前面

系列文章

帶有標准查詢運算符的Lambda

Lambda中類型推斷

Lambda表達式中變量作用域

異步Lambda

總結

寫在前面

上篇文章介紹了Lambda的基本概念以及匿名方法,本篇繼續介紹Lambda的一些內容,既然學了,就要總結的全面一點。

系列文章

Linq之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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM