LinQ實戰學習筆記(三) 序列,查詢操作符,查詢表達式,表達式樹

来源:http://www.cnblogs.com/hackpig/archive/2016/08/30/5821055.html
-Advertisement-
Play Games

序列 延遲查詢執行 查詢操作符 查詢表達式 表達式樹 (一) 序列 先上一段代碼, 這段代碼使用擴展方法實現下麵的要求: 取進程列表,進行過濾(取大於10M的進程) 列表進行排序(按記憶體占用) 只保留列表中指定的信息(ID,進程名) 為了能清楚理解上面代碼的內部動作,我們需要介紹幾組概念. 1. I ...


  • 序列
  • 延遲查詢執行
  • 查詢操作符
  • 查詢表達式
  • 表達式樹

 

(一) 序列

先上一段代碼,

這段代碼使用擴展方法實現下麵的要求:

  • 取進程列表,進行過濾(取大於10M的進程)
  • 列表進行排序(按記憶體占用)
  • 只保留列表中指定的信息(ID,進程名)
1             var res = Process.GetProcesses()
2                 .Where(s => s.WorkingSet64 > 10 * 1024 * 1024)
3                 .OrderByDescending(s => s.WorkingSet64)
4                 .Select(s => new { ID = s.Id, Name = s.ProcessName });

 為了能清楚理解上面代碼的內部動作,我們需要介紹幾組概念.

 

1.  IEnumerable<T>介面

Process.GetProcesses()的返回值是一個Process的數組,而在C#中,所有數組對象均實現了IEnumerable<T>介面.

IEnumerable<T>介面之所以重要,是因為 上面代碼中的Where, OrderByDescending, Select 等LINQ中的標準查詢操作符都需要使用該類型的對象做為參數.

那麼,上面代碼中的Where, OrderByDescending, Select 是哪裡來的呢? 它們是擴展方法, 基於IEnumerable<T>介面類型的擴展方法.

在LINQ中, 術語"序列" 就是指所有實現了IEnumerable<T>介面的對象.

 

我們給出Where擴展方法的實現代碼:

 1         public static IEnumerable<TSource> Where<TSource>(
 2             this IEnumerable<TSource> source,
 3             Func<TSource, Boolean> predicate)
 4         {
 5             foreach (TSource element in source)
 6             {
 7                 if (predicate(element))
 8                     yield return element;
 9             }
10         }

其第一參數中的this關鍵字就證明瞭它是一個擴展方法,參數類型就是IEnumerable<T>.

關鍵字yield return 就構成了一個迭代器.

我們來看一下迭代器的背景知識.

 

2. 迭代器

 從結果的角度看,迭代器與一個返回集合數據的傳統方法沒有什麼區別,因為都是返回按一序列排列的值.

比如下麵的代碼,就返回一個集合的值.

1         int[] OneTwoThree()
2         {
3             return new[] { 1, 2, 3 };
4         }

不過,C#中的迭代器的行為卻非常特殊.迭代器將不會一次性返回整個集合中的所有值.而是每次返回一個.這樣的設計減少了記憶體需求.

我們構建一個迭代器的例子,看一看這個特性.

 1   private void button2_Click(object sender, EventArgs e)
 2         {
 3             foreach (var m in OneTwoThree())
 4             {
 5                 Console.WriteLine(m);
 6             }
 7         }
 8         static IEnumerable<int> OneTwoThree()
 9         {
10             Console.WriteLine("returning 1");
11             yield return 1;
12             Console.WriteLine("returning 2");
13             yield return 2;
14             Console.WriteLine("returning 3");
15             yield return 3;
16         }

 運行結果如下圖

 

可以看到,函數OneTwoThree直到執行完最後一條語句之後才完整退出.

每次遇到yield return語句時,該方法都向調用者返回一個值.

foreach迴圈收到這個值後進行了處理,然後控制權又交回給迭代器方法OneTwoThree方法,由它給出下一個元素.

看起來好像兩個方法在同時運行.這也正是可以將.NET中的迭代器當作是一類輕量級的協同程式(coroutine)的原因.

 

(二) 延遲查詢執行

LINQ查詢語句非常依賴於延遲查詢執行機制,惹是缺少了這個機制,LINQ的執行效率將會大大降低.

來看一段代碼:

 1  static double Square(double n)
 2         {
 3             Console.WriteLine("計算 Square(" + n + ")...");
 4             return Math.Pow(n, 2);
 5         }
 6         private void button3_Click(object sender, EventArgs e)
 7         {
 8             int[] numbers = { 1, 2, 3 };
 9             var res = from n in numbers
10                       select Square(n);
11             foreach (var m in res)
12                 Console.WriteLine(m);
13         }

運行結果如下:

結果可以看到,明顯該查詢並不是一次性執行完畢的.只有在迭代到某一項時,查詢才開始求出這一項的值.

這就是所謂的查詢延遲執行的機制在發揮作用.

我們來討論一下其中的原理:

var res = from n in numbers
                      select Square(n);

上面的LINQ查詢在編譯後,實際上變成了這樣的:

 IEnumerable<double> res = Enumerable.Select<int, double>(numbers, n => Square(n));

也就是LINQ查詢轉為一系列擴展方法的調用,其中的Enumerable.Select方法正是一個迭代器--這也就是其實現了延遲執行的原理.

如果我們需要查詢強制立即執行,可以通過調用ToList方法來實現.

我們把上面的代碼改動一下:

1   private void button4_Click(object sender, EventArgs e)
2         {
3             int[] numbers = { 1, 2, 3 };
4             var res = from n in numbers
5                       select Square(n);
6             foreach (var m in res.ToList())
7                 Console.WriteLine(m);
8         }

可以看到結果就不同了:

可以見到是先得到查詢的結果,最後才把結果迭代輸出的.

 

(三) 查詢操作符

上面代碼所示的 Where,OrderByDescending, Select這些擴展方法 包含有共同的特性:

  • 操作於可被迭代的集合對象之上
  • 允許管道形式的數據處理
  • 依賴於延遲執行

正是上面這些特征讓這些擴展方法能用於編寫查詢.因此這些擴展方法也稱為"查詢操作符"

查詢操作符是LINQ的核心,甚至比語言方面的特性(比如查詢表達式)更重要.

 下圖是按照操作類型分組的標準查詢操作符:

 

(四) 查詢表達式

開往篇的程式是使用查詢操作符實現的.再次引用一下:

1   var res = Process.GetProcesses()
2                 .Where(s => s.WorkingSet64 > 10 * 1024 * 1024)
3                 .OrderByDescending(s => s.WorkingSet64)
4                 .Select(s => new { ID = s.Id, Name = s.ProcessName });

另一種語法則讓LINQ查詢更像是SQL的查詢語句.

1   var res = from s in Process.GetProcesses()
2                       where s.WorkingSet64 > 10 * 1024 * 1024
3                       orderby s.WorkingSet64 descending
4                       select new { ID = s.Id, Name = s.ProcessName };

上面的這種寫法就叫做查詢表達式,或者查詢式語法.

這兩種代碼的寫法從語義上來講是完全相同的,而且實現的功能也一致.

查詢表達式是由C#語言提供的語言級特性,一種語法糖,這種語法類似於SQL,它可以操作於一個或者多個數據源之上,併為這些數據源應用若幹個標準或者自定義的查詢操作符.在上面的示例代碼中,使用了3個標準的查詢操作符:Where, orderByDescending以及Select.

在使用查詢表達式語法時,編譯器會自動將其轉化為對標準查詢操作符的調用.

查詢表達式存在的最主要意義在於,它能夠大大簡化查詢所需要的代碼,並提高查詢語句的可讀性(類似熟悉的SQL).

 

下圖是查詢表達式的完整語法:

標準查詢操作符與查詢表達式的關係,見下表所示:

通過上表可以看到,不是每一個操作符都有與之對應的C#關鍵字.在前面那個簡單的查詢中,我們當然完全可以使用語言所提供的關鍵字實現.不過對於那些較為複雜的查詢來說,我們將不得不直接調用查詢操作符完成.

因為查詢表達式最終都會被編譯成各個標準操作符的調用.因此如果願意的話,完全可以只用查詢操作符編寫所有查詢語句,根本不理會查詢表達式的存在.

 

(五) 表達式樹

 Lambda表達式在前面提到過它的主要作用之一是實現匿名委托.如下例:

Func<int,bool> isOdd=i=>(i & 1)==1;

但是,Lambda表達式也能夠以數據的形式使用,這正是表達式樹所要求的.

當把代碼改成下麵這樣時,我們就無法以委托的形式來使用isOdd了.因為在這裡isOdd並不是委托,而是個表達式樹.

Expression<Func<int,bool>> isOdd =i => (i & 1) ==1;

編譯器不會把上面的Lambda表達式換成IL代碼,而是會構造出一個樹狀的,用來表示該表達式的對象.

但是需要註意的是:只有那些帶有表達式體的Lambda表達式才能夠用於表達式樹.主體部分是語句的Lambda表達式則沒有類似的支持.

例如,下麵第1行代碼可以用來生成一顆表達式樹,因為其帶有表達式體.

第2行的就不能,因為它的主體部分是一個語句.

1 Expression<Func<Object, Object>> identity = o=>o;
2 Expression<Func<Object, Object>> identity = o=>{ return o;};

當編譯器看到某個Lambda表達式賦值給類型為Expression<>的變數時,就會將其編譯成一系列工廠方法的調用,這些工廠方法將在程式運行時動態地構造出表達式樹.

下麵就是編譯器為上述表達式自動生成的代碼:

1   ParameterExpression i = Expression.Parameter(typeof(int), "i");
2             Expression<Func<int, bool>> isOdd =
3                 Expression.Lambda<Func<int, bool>>(
4                 Expression.Equal(
5                 Expression.And(
6                 i,
7                 Expression.Constant(1, typeof(int))),
8                 Expression.Constant(1, typeof(int))),
9                 new ParameterExpression[] { i });

上面的代碼是可以手工編寫的,但是編譯器可以代勞.

表達式樹將在程式運行中動態構造,不過一旦構造完成,則無法被再次修改.

表達式樹在第5章中用以創建動態查詢這種高級場景上得到了應用.

上面的表達式樹,在記憶體中以樹的數據結構存儲,它表示解析了後的Lambda表達式,如下圖:

上面的表達式樹,還可以"逆向"編譯成委托方法:

1    Expression<Func<int, bool>> isOddExpression = i => (i & 1) == 1;
2             Func<int, bool> isOddCompiledExpression = isOddExpression.Compile();

這時候,上面的isOddCompiledExpression和下麵的委托isOdd就完全相同了,它們生成的IL代碼就沒有任何區別了.

Func<int,bool> isOdd=i=>(i & 1)==1;

為什麼要使用表達式樹呢?

實際上,表達式樹就是一顆抽象語法樹(AST).抽象語法樹用來表示一段經過解析的代碼.在上面例子中,這顆樹就是C#對於Lambda表達式解析後的結果.這樣做的目的是便於其它代碼對該表達式樹進行分析,並執行一些必要的操作.

表達式樹可以在運行時傳遞給其它的工具,隨後這些工具可以根據該樹開始執行查詢,或者是將其轉化為其它形式的代碼,例如LINQ to SQL中的SQL語句.

 

最後我們來看看表達式樹執行延遲查詢執行的方法:

引用之前LINQ to SQL例子中的代碼:

1  var contacts =
2               from contact in db.GetTable<HelloLinqToSql.Contact>()
3               where contact.City == "武漢"
4               select contact;
5 
6             Console.WriteLine("查找在武漢的聯繫人"+Environment.NewLine);
7             foreach (var contact in contacts)
8                 Console.WriteLine("聯繫人: " + contact.Name.Trim()+" ID:"+contact.ContactID);

我們知道使用IEnumerable<T>迭代器可以產生延遲查詢的行為,在上面代碼中 contacts變數的類型不是IEnumerable<T>,而是IQueryable<Contact>.

處理IQueryable<Contact>數據與處理序列完全不同.IQueryable<Contact>的實例將要接受一棵表達式樹,由些分析出下一步將要進行的操作.

在上面代碼中,一旦我們開始遍歷contacts變數,那麼程式就會開始分析其中包含的表達式樹,隨後生成SQL語句並執行,最後該SQL語句的返回結果以Contact對象集合的形式給出.

與基於IEnumerable<T>的序列相比, IQueryable<Contact>更加強大,因為程式可以根據表達式樹的分析結果進行智能地處理.通過查看某個查詢的表達式樹,編譯器即可智能地進行推斷併進行大量的優化.IQueryable<Contact>和表達式樹的組合將給我們帶來更強大的可定製能力.

 

原創文章,出自"博客園, 豬悟能'S博客" : http://www.cnblogs.com/hackpig/

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、Linux目錄結構 你想知道為什麼某些程式位於/bin下,或者/sbin,或者/usr/bin,或/usr/sbin目錄下嗎?例如,less命令位於/usr/bin目錄下。為什麼沒在/bin中,或/sbin,或/usr/sbin目錄中?所有這些目錄之間有什麼不同? 在這篇文章中,讓我們回顧一下L ...
  • 最終的效果圖 下麵開始幹活:生成幫助文檔 一、創建 WebApi 項目 二、找到 HelpPageConfig.cs 並取消代碼註釋 三、對項目單擊右鍵,選擇屬性,按圖操作 四、啟動項目,輸入Url: localhost:{埠號}/help 【註】Description 沒有顯示內容是因為我們沒有 ...
  • 本次主要分享幾個場景的處理代碼,有更好處理方式多多交流,相互促進進步;代碼由來主要是這幾天使用前端Ace框架做後臺管理系統,這Ace是H5框架裡面的控制項效果挺多的,做相容也很好,有點遺憾是控制項效果基本都是寫一起的,分離起來挺麻煩的;這次主要說的是後端代碼,以後可以分享下這個框架的使用。 以上是個人的 ...
  • 本文版權歸博客園和作者本人共同所有,轉載和爬蟲請註明本系列分享地址:http://www.cnblogs.com/tdws/p/5815735.html 上一篇文章的不合理之處,已經有所修改。 今天分享的是Hash散列數據類型操作,不過我也覺得有了前兩篇的基礎搭建後,你就能自己按照StackExch ...
  • Asp.Net MVC5+ EntityFramework6.0 db first + Autofac(IOC) + SqlServer2014還請大家多多指教啦 anneke.cn ...
  • 前戲:針對上一篇列出來的功能點,今天和大家分享下這個自定義的公式是怎麼設計的,由於我的第一篇博客在首頁被管理員移走了,大家可以點擊這裡來跳轉,看下第一篇的目錄結構。本人作為老菜鳥,和大家分享的也是一些老菜鳥的想法,大神千萬別噴我. 設計背景:當初為什麼要設計這個自定義的計算公式呢,原因就是,這個價格 ...
  • ASP.NET Core開發Docker部署,.NET Core支持Docker 部署運行。我們將ASP.NET Core 部署在Docker 上運行。 大家可能都見識過Docker ,今天我們就詳細瞭解一下Docker的用途,以及真實的應用場景。 Docker源於PaaS,PaaS的應用場景即是D ...
  • 從淘寶UWP第一版發佈到現在,已經有十個月了,期間收到了用戶各種各樣的反饋,感謝這些用戶的反饋,指導我們不斷的修正、完善應用。但是也有一部分需求或建議,由於資源或技術的限制,目前確實無法做到,只能對廣大Win10er說聲抱歉了。下麵針對幾種用戶常提到的反饋做下說明。 最經常的反饋是為什麼在某某版本上 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...