LINQ 延遲查詢的原因

延遲查詢

在運行 LINQ 中的某些擴展方法進行集合的查詢時,查詢不會立即運行。只有當運行至 foreach 對查詢結果進行遍歷時,或者對查詢結果調用 ToList() 方法等情況時,查詢纔會真正的運行。

我們以 Where() 方法爲例進行研究。考察如下代碼:

var names = new List<string> {"Nino", "Alberto", "Juan", "Mike", "Phil"};
var namesWithJ = names.Where(n=>n.StartsWith("J")).OrderBy(n => n);

Console.WriteLine("第一次查詢");
foreach (string name in namesWithJ)
{
     Console.WriteLine(name);
}
Console.WriteLine();
names.Add("John");
names.Add("Jim");
names.Add("Jack");
names.Add("Denny");
Console.WriteLine("第二次查詢");
foreach (string name in namesWithJ)
{
     Console.WriteLine(name);
}

輸出結果如下:

第一次查詢
Juan

第二次查詢
Jack
Jim
John
Juan

我們的查詢只定義了一次,兩次查詢卻有不同的輸出。這是因爲,在遇到 foreach 迭代查詢結果時,Where 查詢纔去從數據源中查詢數據,返回給調用者。

現在,我們在查詢語句後面加一個 ToList() 方法,那麼兩次查詢輸出的都是 Juan 了。

yield return 語句

我們研究一下 Where() 方法,發現它返回的是一個 IEnumerable<T> 對象。它是個枚舉器。也就是說,我們的查詢語句並沒有在內存中產生一個集合,而是一個枚舉器。
Where() 方法的內部實現原理如下:

public static IEnumerable<T> Where<T> (this IEnumerable<T> source, Func<T, bool> predicate)
{
    foreach (T item in source)
    {
        if (predicate(item))
            yield return item;
    }
}

yield 語句是 C# 2.0 添加的用於方便創建枚舉器的關鍵字。yield return 語句返回集合的一個元素,並移動到下一個元素上。 從 foreach 中依次訪問每一項時,就會調用枚舉器進行枚舉。這樣就可以迭代大量的數據,而無需一次把所有的數據都讀入內存中。迭代器方法運行到 yield return 語句時,會返回一個 expression,並保留當前在代碼中的位置。 下次調用迭代器函數時,將從該位置重新開始執行

Enumerator 接口(或其泛型形式)定義了一個狀態機。它內部擁有一個成員記錄當前的狀態(當前遍歷到的元素 Current),擁有 MoveNext() 方法和 Reset() 方法,這樣就可以不斷迭代下去。

返回一個枚舉器而不是一個集合,這樣做有一些好處。例如,如果我們要從數據庫中查詢一億個數據,然後將這些數據輸出。如果將查詢結果放到集合中,然後再輸出,那麼我們不得不等到這一億個數據全部查寫入內存中後才能輸出,這樣就有一個阻塞的過程。而如果通過枚舉器,那麼在執行輸出動作的時候,纔會真正從數據庫中進行查詢,並且沒查詢到一個數據,就可以立即輸出它,然後接着從數據庫中查詢下一條。這樣,用戶會覺得數據的顯示沒有延遲。

參考文獻

[1] Where 方法
[2] IEnumerator 接口
[3] 迭代器
[4] yield 參考
[5] C#中yield return用法分析

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章