[.NET]快快樂樂學LINQ系列前哨戰-Fluent Interface的效果
前言
在上一篇文章:快快樂樂學LINQ系列前哨戰-IEnumerable, IEnumerable<T>與Enumerable 中提到了,System.Linq這個namespace的精髓,就在於Enumerable這個類別上,而且最主要的職責,便是針對IEnuemrable<T>這個介面的擴充。
也就是,一旦某個資料集合符合IEnumerable<T>,或是轉型為IEnumerable<T>,就可以享用那一堆LINQ to Objects的功能。
而在使用LINQ的過程中,相信大家很常看到類似下面的程式碼:
不知道讀者朋友們有沒有疑惑過,為什麼在使用LINQ時,例如圖中的data,可以在呼叫Where()之後,再呼叫Select(),再呼叫OrderByDescending()?為什麼可以設計出類似jQuery上method chain的使用方式?對啦,因為他們使用了Fluent Interface的方式來設計一些method。
Fluent Interface
針對Fluent Interface的介紹,小朱之前已經寫過一篇介紹的挺詳盡的文章:[.NET] Fluent Interface: 實作 Method Chaining 又不會有耦合性的作法,請各位可以參考了解一下。
其實原理相當簡單,就是因為原本的物件型別是IEnumerable<T>,而這些方法回傳的型別為IEnumerable<TResult>,他們都屬於IEnumerable<T>,所以回傳後的型別,還可以使用Enumerable類別上,針對IEnumerable<T>的擴充方法。
注意,這邊特地標示出來,原本的物件型別是IEnumerable<T>,而回傳的型別是IEnumerable<TResult>,用意就是在說明,兩個泛型T,可以不同。
以上述的例子來說,data的型別是IEnumerable<int>,經過Where()後,仍是IEnumerable<int>,但經過Select()之後,其型別是IEnuemrable<匿名型別>,而OrderByDescending()回傳的,也是IEnuemrable<匿名型別>。
從MSDN的方法簽章來看:
-
Where:
亮黃色的部份,代表回傳的型別為IEnumerable<TSource>,也就是與呼叫Where()方法的執行個體this IEnumerable<TSource>,相同的型別。以上面的例子來說,TSource為int。
-
Select:
亮黃色的部份,代表回傳的型別為IEnumerable<TResult>,而TResult是由selector這一個參數的泛型委派方法,來決定方法回傳的型別為何。以上面的例子來說,TSource為int,TResult為匿名型別。 -
OrderByDescending:
亮黃色的部份,代表回傳的型別為IOrderedEnumerable<TSource>,可以看到泛型的部份,與Where類似,也就是與呼叫OrderByDescending()方法的執行個體this IEnumerable<TSource>,相同的型別。但介面改變了,介面從IEnumerable<TSource>,改變為IOrderedEnumerable<TSource>。這是否代表OrderByDescending()方法之後,就不能再用IEnuemrable<T>上的擴充方法呢?
答案是:仍然可以使用IEnumerable<T>上的擴充方法。
因為IOrderedEnumerable<T>介面,是繼承(或者稱為擴充 extend)自IEnumerable<T>,如下圖所示。所以,IOrderedEnumerable<T>是IEnumerable<T>的一種,當然可以使用父類的方法。
注意!從上面這個例子來說,其實每一個方法執行完後,回傳的物件集合,其實都與data無關了。基本上回傳的都是一組新的物件,只是回傳的型別通常都是IEnumerable<T>的一種,所以可以方便使用者,用很直覺的呼叫方式,來針對原本的data,進行篩選、排序與組合等等相關動作。
再搭配編譯器針對效能的優化,讓LINQ這樣的expression tree能擁有延遲執行的特性,整個執行的過程,看起來就更像是針對原本的物件進行一系列的操作。
結論
透過上面一個簡單的例子,以及MSDN上相關的方法簽章說明,大家應該對Fluent interface不再這麼陌生,也不會再納悶明明LINQ方法中回傳型別與來源型別已經不一樣了,為什麼還可以用method chain的方式呼叫。
當然,不是所有IEnumerable<T>上的擴充方法,都會回傳IEnumerable<T>,但絕大部分會使用到的方法,都會回傳IEnumerable<T>,來保持讓使用者可以繼續呼叫其他方法,得到他們想要的結果。
針對上面的幾個方法,我們會在後續的文章裡,自己動手做做看。針對Func<T1, T2, …, TResult>這類的泛型委派,讀者朋友們看不懂也不用緊張,當把整個前哨戰的系列文章貫穿起來時,您就會了解到,原來只是透過這些基礎,就能組合出這麼好用的LINQ。
blog 與課程更新內容,請前往新站位置:http://tdd.best/