LINQ 延遲查詢(延遲執行) 的小問題 + 小測試

今天工作的時候 發生個小BUG,查了半天

才想到是 LINQ 延遲查詢 造成的

LINQ 查詢結果 搭配 foreach 作資料操作上的小問題


LINQ 延遲查詢,有很多前輩都有寫文章作說明講解

雖然有些細節我還無法吸收

小朱® 的技術隨手寫-前輩的 [.NET] LINQ 的延遲執行 (Deferred Execution)

In 91-前輩的 [.NET]快快樂樂學LINQ系列前哨戰-延遲執行 (Deferred Execution)

兩位前輩講解比較深入

而實際應用上會遇到的問題,可以參考

Huan-Lin 學習筆記-前輩的 《C# 本事》摘錄:LINQ (2)

參考資料網路上還有許多,我就不一一列表

以下是我發生的情況


簡化模擬出我工作的情況,當然條件沒這麼簡單,因為物件的參照,以及Where條件的複雜

這BUG我查了許久,才想到是延遲查詢造成的

List<int> testDatas = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
 
int condition = 1;
 
var selectDatas = testDatas.Where(i => i > condition);
 
int times = 0;
foreach (var oneData in selectDatas)
{
    ++times;
    Console.WriteLine($"第{times}次 查詢結果數量:{selectDatas.Count()}");
 
    condition += 2;
 
    Console.WriteLine($"變更條件後 查詢結果數量:{selectDatas.Count()}");
    Console.WriteLine("=======");
}

正常來想,已查詢後的結果,就算去動到查詢條件

應該也不會變更到查詢結果

會認為結果如下圖.1

但是因為延遲查詢的特性​

上面的程式碼執行結果會如下圖.2

並不會如預想的查詢結果不變

解決方是很簡單,就是把查詢結果ToList就可以避免變化

var selectDatas = testDatas.Where(i => i > condition).ToList();

執行結果就會如圖.1

為了以後不要再發生同樣問題

只要記的把使用LINQ後的結果

如果要使用foreach 都轉換成List即可


但這時就想到 基本上LINQ延遲查詢特性

它會再 foreach 每次都查詢一次

所以效能到底是如何呢

我簡單的測試一下,測試如下

List<int> testDatas = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
Stopwatch sw = new Stopwatch();
for (int j = 0; j < 10; ++j)
{
    // 第一種測試
    sw.Reset();
    sw.Start();
    int condition = 1;
    var selectDatas = testDatas.Where(i => i > condition);
    for (int i = 0; i < 1000000; ++i)
    {
        foreach (var t in selectDatas)
        {
            int temp = t;
            ++temp;
        }
    }
    sw.Stop();
    Console.WriteLine($"第{j}次 直接查詢後foreach {sw.ElapsedMilliseconds}");
    // 第二種測試
    sw.Reset();
    sw.Start();
    selectDatas = testDatas.Where(i => i > condition).ToList();
    for (int i = 0; i < 1000000; ++i)
    {
        foreach (var t in selectDatas)
        {
            int temp = t;
            ++temp;
        }
    }
    sw.Stop();
    Console.WriteLine($"第{j}次 查詢後ToList {sw.ElapsedMilliseconds}");
    // 第三種測試
    sw.Reset();
    sw.Start();
    selectDatas = testDatas.Where(i => i > condition);
    for (int i = 0; i < 1000000; ++i)
    {
        var tempList = selectDatas.ToList();
        foreach (var t in tempList)
        {
            int temp = t;
            ++temp;
        }
    }
    sw.Stop();
    Console.WriteLine($"第{j}次 查詢後在迴圈裡ToList {sw.ElapsedMilliseconds}");
    Console.WriteLine("=====");
}

結果如下圖


結論

ToList後再去 foreach效能比較好,理由也很簡單

因為並不會每次在去重新查詢。

所以建議查詢結果需要去 foreach作資料操作 都先轉換成List

當然ToList轉換一定也有他的效能消耗。所以要判斷在哪邊使用

像我第三種測試 故意在迴圈裡面 ToList 速度明顯變慢很多