Linq系列—linq語法和擴展方法關係

轉載至NET深入解析LINQ框架(四:IQueryable、IQueryProvider接口詳解)

我們知道LINQ所支持的查詢範圍主要在IEnumerable、IQueryable這兩個方面,對於我們想要擴展LINQ的查詢能力也主要集中在這兩塊。很多時候我們在編寫應用框架的時候,都會自己去實現IEnumerble對象,一般不會用系統提供的集合類,這樣爲了框架的OO性,上下文連貫性,更模型化。如果應用框架具備一定的查詢能力是不是很方便些。比如你在開發一個關於數據密集性的框架,可能不是實時的持久化,但是能在外部提供某種查詢工具來查詢內存中的數據,所以這個時候需要我們能擴展LINQ的Object查詢能力。這一節我們就來學習怎麼擴展Linq to Object。
LINQ查詢Object是基於IEnumerable對象的,不是集合對象有什麼好查的。對於IEnumerable對象的LINQ查詢是Enumerable靜態對象在支撐着,然後通過匿名表達式來表示邏輯,這樣就能順其自然的查詢集合。那麼我們該如何下手擴展Linq to Object?其實也就是兩點可以擴展,要麼提供擴展方法來擴展IEnumerable對象,當然你別企圖想讓VS支持某種關鍵字讓你對應擴展方法。還有就是繼承IEnumerable對象讓我們自己的集合類型具備LINQ的強類型的查詢能力。當然具體要看我們需求,從技術角度看目前只有這兩點可以擴展。
如果我們使用擴展方法那麼只能是擴展IEnumerable對象,這沒有問題。我們可以很方便的在LINQ的表達式中調用我們自己的擴展方法,讓自己的方法跟着一起鏈式查詢。如果我們從繼承IEnumerable對象擴展,那麼情況會有點小複雜,你的擴展方法中要擴展的對象一定要具體的給出對象的定義才行,如果你擴展的對象不能和繼承的對象保持一直,那麼你將斷掉所有的擴展方法。[王清培版權所有,轉載請給出署名]
2.1】.通過添加IEnumerable對象的擴展方法
下面我們通過具體的例子來分析一下上面的理論,先看看通過擴展方法來擴展系統的IEnumerable對象。
代碼段:Order類
///
/// 訂單類型
///
public class Order
{
///
/// 訂單名稱
///
public string OrderName { get; set; }
///
/// 下單時間
///
public DateTime OrderTime { get; set; }
///
/// 訂單編號
///
public Guid OrderCode { get; set; }
}

這是個訂單類純粹是爲了演示而用,裏面有三個屬性分別是”OrderName(訂單名稱)”、”OrderTime(下單時間)”、”OrderCode(訂單編號)”,後面我們將通過這三個屬性來配合示例的完成。
如果我們是直接使用系統提供的IEnumerable對象的話,只需要構建IEnumerable對象的擴展方法就能實現對集合類型的擴展。我假設使用List來保存一批訂單的信息,但是根據業務邏輯需要我們要通過提供一套獨立的擴展方法來支持對訂單集合數據的處理。這一套獨立的擴展方法會跟隨着當前系統部署,不作爲公共的開發框架的一部分。這樣很方便也很靈活,完全可以替代分層架構中的部分Service層、BLL層的邏輯代碼段,看上去更爲優雅。
再發散一下思維,我們甚至可以在擴展方法中做很多文章,把擴展方法納入系統架構分析中去,採用擴展方法封裝流線型的處理邏輯,對業務的碎片化處理、驗證的鏈式處理都是很不錯的。只有這樣才能真正的讓這種技術深入人心,才能在實際的系統開發當中去靈活的運用。
下面我們來構建一個簡單的IEnumerable擴展方法,用來處理當前集合中的數據是否可以進行數據的插入操作。
代碼段:OrderCollectionExtent靜態類
public static class OrderCollectionExtent
{
public static bool WhereOrderListAdd(this IEnumerable IEnumerable) where T : Order
{
foreach (var item in IEnumerable)
{
if (item.OrderCode != null && !String.IsNullOrEmpty(item.OrderName) && item.OrderTime != null)
{
continue;
}
return false;
}
return true;
}
}

OrderCollectionExtent是個簡單的擴展方法類,該類只有一個WhereOrderListAdd方法,該方法是判斷當前集合中的Order對象是否都滿足了插入條件,條件判斷不是重點,僅僅滿足例子的需要。這個方法需要加上Order類型泛型約束才行,這樣該擴展方法纔不會被其他的類型所使用。
List orderlist = new List()
{
new Order(){ OrderCode=Guid.NewGuid(), OrderName=”水果”, OrderTime=DateTime.Now},
new Order(){ OrderCode=Guid.NewGuid(), OrderName=”辦公用品”,OrderTime=DateTime.Now}
};
if (orderlist.WhereOrderListAdd())
{
//執行插入
}

如果.NET支持擴展屬性【不過微軟後期肯定是會支持屬性擴展的】,就不會使用方法來做類似的判斷了。這樣我們是不是很優雅的執行了以前BLL層處理的邏輯判斷了,而且這部分的擴展方法是可以動態的更改的,完全可以建立在一個獨立的程序集當中。順便在擴展點使用思路,在目前MVVM模式中其實也可以將V中的很多界面邏輯封裝在擴展方法中來減少VM中的耦合度和複雜度。包括現在的MVC都可以適當的採用擴展方法來達到更爲便利的使用模式。
但是大部分情況下我們都是針對所有的IEnunerale類型進行擴展的,這樣可以很好的結合Linq的鏈式編程。原理就這麼多,根據具體項目需要適當的採納。[王清培版權所有,轉載請給出署名]
2.2】.通過繼承IEnumerable接口
我想大部分的情況下我們都是直接使用IEnumerable的實現類,但是在編寫系統組件、框架的時候一般都是要自己去實現自己的迭代器類的。那麼這個時候的擴展方法還能作用於我們繼承下來的類,這是相當方便的,不知不覺我們自己擴展的組件將也會支持Linq的查詢。但是這個時候應該適當的控制你針對繼承下來的類的擴展,擴展方法應該是面向你內部使用的,不能污染到外部的對象。
我們繼續看例子,該例子是針對繼承IEnumerable來分析使用方式;
public class OrderCollection : IEnumerable
{
List orderList;
public OrderCollection()
{
orderList = new List() {
new Order(){ OrderCode=Guid.NewGuid(),OrderName=”訂單1”, OrderTime=DateTime.Now},
new Order(){ OrderCode=Guid.NewGuid(),OrderName=”訂單2”, OrderTime=DateTime.Now},
new Order(){ OrderCode=Guid.NewGuid(),OrderName=”訂單3”, OrderTime=DateTime.Now}
};
}

   public IEnumerator<Order> GetEnumerator()   
   {   
       foreach (var order in orderList)   
       {   
           yield return order;   
       }   
   }   
   System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()   
   {   
       foreach (var order in orderList)   
       {   
           yield return order;   
       }   
   }   

}

這是個Order集合類型OrderCollection類,該類專門用來存放或處理Order類的。不管是從兼容.NET2.0或者其他方面考慮都可能將集合的類型封裝在.NET2.0版本的程序集中,在.NET2.0之上的版本都會提供擴展版本的程序集,這個時候我們的擴展方法要專門針對OrderCollection去編寫,否則就會造成 IEnumerable對象的污染。
public static OrderCollection GetOutOrderCollection(this OrderCollection OrderColl)
{
return OrderColl;
}
這個時候會很乾淨的使用着自己的擴展方法,不會造成大面積的污染。當然一般都是依賴倒置原則都會有高層抽象,不會直接擴展實現類,這裏只是簡單的介紹。[王清培版權所有,轉載請給出署名]
2.3】.詳細的對象結構圖
這個小結主要將IEnumerable及它的擴展方法包括Linq的查詢進行一個完整的結構分析,將給出詳細的對象結構導圖。
對象靜態模型、運行時導圖:
這裏寫圖片描述
上圖中的關鍵部分就是i==10將被封裝成表達式直接送入Where方法,而select後面的i也是表達式【(int i)=>i】,也將被送入Select方法,這裏就不畫出來了。順着數字序號理解,IEnumerable是Linq to Object的數據源,而Enumerable靜態類是專門用來擴展Linq查詢表達式中的查詢方法的,所以當我們編寫Linq查詢IEnumerable集合是,其實是在間接的調用這些擴展方法,只不過我們不需要那麼繁瑣的去編寫Lambda表達式,由編輯器幫我們動態生成。
小結:本節主要講解了Linq to Object的原理,其實主要的原理就是Lambda表達式傳入到Enumerable擴展方法當中,然後形成鏈式操作。Linq 只是輔助我們快速查詢的語言,並不是.NET或者C#的一部分,在任何.NET平臺上的語言中都可以使用。下面我們將重點分析Linq to Provider,這樣我們才能真正的對LINQ進行高級應用。[王清培版權所有,轉載請給出署名]
3.】.實現IQueryable 、IQueryProvider接口
這篇文章的重點就是講解IQueryable、IQueryProvider兩個接口的,當我們搞懂了這兩個接口之後,我們就可以發揮想象力的去實現任何一個數據源的查詢。IQueryable、IQueryProvider兩接口還是有很多值得我們研究的好東西,裏面充斥大量的設計模式、數據結構的知識,下面我們就來慢慢的分析它的美。
IQueryable接口是Linq to Provider的入口,非常有意思的是它並不是一個IQueryable來支撐一次查詢。我們在編寫Linq語句的時候一般都是 where什麼然後select 什麼,至少連續兩個擴展方法的映射調用,但是朋友你知道它內部是如何處理的嗎?每當Where過後緊接着Select他們是如何關聯一個完整的查詢的?IQueryable並非IEnumerable對象,無法實時的做出處理然後將結果返回給下一個方法接着執行。那麼它如何將片段性的執行方法串成一個整的、完整的查詢?下面我們將逐個的分析這其中要涉及到的模式、數據結構、框架原則,這些搞懂了之後代碼都是模型的表現,也就順其自然的明白了。
3.1】.延遲加載IEnumertor對象(提高系統性能)
延遲加載的技術其實在Linq之前就已經在使用,只不過很少有人去關注它,都被隱藏在系統框架的底層。很多場合下我們需要自己去構建延遲加載特性的功能,在IEnumerable對象中構建延遲基本上是通過yield return 去構建一個狀態機,當進行迭代的時候才進行數據的返回操作。那麼在IQueryable中是通過執行Provider程序來獲取數據,減少在一開始就獲取數據的性能代價。IQueryable繼承自IEnumerable接口,也就是可以被foreach語法調用的,但是在GetEnumerator方法中才會去執行提供程序的代碼。我們來分析一下IQueryable接口的代碼。
public IEnumerator GetEnumerator()
{
return (Provider.Execute(Expression) as IEnumerable).GetEnumerator();
}

   System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()   
   {   
       return (Provider.Execute(Expression) as IEnumerable).GetEnumerator();   
   }  

這是IQueryable接口中從IEnumerable繼承下來的兩個返回IEnumerator接口類型的方法,在我們目前使用的Linq to Sql、Linq to Entity中都會返回強類型的集合對象,一般都不會實時的進行數據查詢操作,如果要想實時執行需要進行IQueryable.Provider.Execute方法的直接調用。

我們用圖來分析一下Linq to Provider中的延遲加載的原理;
這裏寫圖片描述
這段代碼不會被立即執行,我們跟蹤一下各個組成部分之間的執行過程;
這裏寫圖片描述
這幅圖重點是IQueryable對象的連續操作,大致原理是每次執行擴展方法的時候都會構造一個新的IQueryable,本次的IQueryable對象將包含上次執行的表達式樹,以此類推就形成了一顆龐大的表達式樹。詳細的原理在下面幾小節中具體分析。[王清培版權所有,轉載請給出署名]

最後Orderlist將是一個IQueryable類型的對象,該對象中包含了完整的表達式樹,這個時候如果我們不進行任何的使用將不會觸發數據的查詢。這就是延遲加載的關鍵所在。如果想立即獲取orderlist中的數據可以手動執行orderlist.Provider.Execute(orderlist.Expression)來獲取數據。
3.2】.擴展方法的擴展對象之奧祕(this IQueryable source)
其實這裏有一個思維陷阱,當我們分析源碼的時候只將焦點集中在擴展方法中的後面參數上,而沒有集中精力考慮擴展方法所擴展的對象本身,看似不同的方法位於不同的地方,其實他們來自一個地方,所在的邏輯對象是一個,但是這恰恰會造成我們分析問題的瓶頸,這裏我們重點的講解一下擴展方法所擴展對象。
我們直接用源碼進行講解吧;
public static IQueryable Select

發佈了21 篇原創文章 · 獲贊 6 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章