這是LINQ(集成化查詢)的繼續及補充,在前面我已經介紹過,在LINQ中,一個重要的特性就是延遲加載,是指查詢操作並不是在查詢運算符定義的時候執行,而是在真正使用集合中的數據時才執行(如:在遍歷集合時調用MoveNext方法時)。下面是一個簡單的實例:
var num = new List<int>(); num.Add(1); IEnumerable<int> query = num.Select(n => n * 10); num.Add(2); foreach (int n in query) Console.WriteLine(n);
結果輸出10 20.在上面的例子中,在創建查詢語句后又向集合中加入新元素,這個新元素最終也出現在查詢結果中。這就是因為查詢語句是在遇到foreach之后才真正執行的,再例如:
Action a = () => Console.WriteLine("Foo"); //沒有在控制台輸出任何內容 a(); //延遲執行,輸出Foo
絕大部分標准的LINQ查詢運算符都具有延遲加載這種特性,但也有例外:
- 那些返回單個元素或返回一個數值的運算符,如First或Count。
- 轉換運算符:ToArray,ToList,ToDictonnary,ToLookup。
以上這些運算符都會觸發LINQ語句立即執行,因為它們的返回值類型不支持延遲加載。
延遲加載的工作原理
LINQ查詢運算符之所以有延遲加載功能,是因為每個返回值不是一般的數組或集合,而是一個經過封裝的序列,這種序列通常情況下並不直接儲存數據元素,它封裝並使用運行時傳遞給它的集合,元素也由其他集合來儲存,它實際上只是維護自己與數據集合的一種依賴關系,當有查詢請求時,再到它以來的序列中進行真正的查詢。查詢運算符實際上是封裝一系列的轉換函數,這種轉換函數可以將與之關聯的數據轉換為各種形式的序列。如果輸出集合不需要轉換的話,那么就不用執行查詢運算符封裝的轉換操作,這個時候查詢運算符實際上就是一個委托,進行數據轉發而已。
例如調用Where運算符的時候,在Where內部所做的操作非常簡單,根據Lambda表達式中指定的查詢條件,對輸入集合重新進行了篩選,保留那些符合條件的元素指針引用,當外部遍歷Where的返回值時,Where回到它所關聯的集合中進行真正的查詢,然后返回查詢結果。
IEnumerable<int> lessThanTen = new int[] { 5, 12, 3 }.Where(n => n < 10);
當執行lessThanTen操作時,實際上,就是使用where運算符對封裝序列進行篩選。
當需要使用一些有特殊功能的運算符時,我們可以自定義實現。例如下面這個實例所示,實現一個自定義的Select運算符:
public static IEnumerable<TResult> Select<TSource,TResult>( this IEnumerable<TSource> source,Func<TSource,TResult> selector) { foreach (TSource element in source) yield return selector(element); }
上面這個方法實際上就是在循環中返回yield類型的元素。所以當調用Select和Where查詢運算符時,內部操作就是創建一個序列,然后將查詢得到的元素存入這個新的序列中。
其實,在LINQ中,延遲加載特性是很重要的,這種設計將查詢的轉換和查詢的執行進行了解耦,這使得我們可以將查詢分成多個步驟來創建,有利於查詢表達式的書寫,而且在執行的時候按照一個完整的結構去查詢,減少了對集合的查詢次數,這種特性對數據庫查詢尤其重要。