LINQ之延遲加載及其原理


       這是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中,延遲加載特性是很重要的,這種設計將查詢的轉換和查詢的執行進行了解耦,這使得我們可以將查詢分成多個步驟來創建,有利於查詢表達式的書寫,而且在執行的時候按照一個完整的結構去查詢,減少了對集合的查詢次數,這種特性對數據庫查詢尤其重要。

 


免責聲明!

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



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