LINQ 簡潔的寫法在 .net 相關的專案裡算是隨處可見,可是它也有一些雷很容易讓人踩中。尤其是當系統要吃的量跟資料越來越大,原本好用的 LINQ 語法可能就會藏著一些雷。
LINQ 裡的 Where, First, FirstOrDefault……這些類似的方法,當我們用來查詢集合時(LINQ to object),有可能在較差的情況下會走到 O(n)(Where 是一定會 O(n))。
O(n) 如果是在筆數不多的情況下,影響並不大。但如果來源的集合物件筆數很多,並且又要反覆的被 loop 使用,這時候它就很有機會卡住打進來的 request。
如果從 trace log 確定瓶頸是因為 LINQ 查詢集合物件在慢,並且是因為反覆的在對一個筆數較多的集合物件反覆查詢,那就可以考慮將集合物件做成像Dictionary or HashSet 這種有 Key 設計的物件。
它可以將時間複雜度從 O(n) 直接變為 O(1),額外的開銷就是要多 allocate heap memory for Dictionary 這個物件。但 CP 值高,因為吃的量可以有悔明顯感覺變多。
LINQ 的 Where、Select、Take 都是用了延遲執行(Deferred Execution)的機制,在編譯時會變成 GetEnumerator 然後搭配 State Machine Pattern。
所以在正常的使用下,它的概念很像是 streaming 一樣,一筆一筆處理並傳遞給下一個動作,儲存空間的使用正常會是 O(1)(先不看來源物件集合佔用的空間)。
範例程式:
IEnumerable<int> enumerable = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var r = enumerable.Where(x=> x > 5);
foreach(var item in r)
{
Console.WriteLine($"{item}");
}
但如果你的專案程式是舊版的 .net,LINQ 鏈結的順序就有可能會讓你踩中雷。
例:在 Where 之後加了一個 OrderBy 然後再 Select。
這會讓程式在 OrderBy 這個 extension 方法就先將前面 Where 所有的元素全部都先取得後,才會再繼續 pass 結果值給下一個 Select 方法。
這會導致原本 O(1) 的複雜度,瞬間在這個動作變成 O(n)。
當集合物件的筆數不多時,這不會是個問題。但當筆數很多的時候,這會是個很不健康的寫法。
在新版的 .net 已經有修掉此問題(不確定從幾版開始),也就是底層會先把整句 LINQ 都讀完,自動來重排一下順序。讓那些 streaming 的擴充方式(e.g. Where)先執行,先篩選完符合條件的元素。
IEnumerable<int> enumerable = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var r = enumerable.Where(x=> x > 5).OrderBy(x => x).Select(x => x);
查了一下資料,OrderBy 這個方法也是會被編譯成 IEnumerator & state maching pattern 的實作。但差異是它就是先堵住,要把前面動作的元素先全部拿到,才會接著動作。所以才會從 O(1) 變成 O(n)。
不管 .net 的版本,在寫 LINQ 的程式時,開發的那個人就是要有留意到這種順序的習慣,避免留了個雷給後面的人。
因為 LINQ 的 deferred execution & state machine 機制,所以 LINQ 其實不會去記那些跑過 extention method 的結果。例:Where, Select。
換句話說,每一次的 GetEnumerator 都會讓這段 LINQ 再重新執行一次。是一種重覆做工,並且極耗效能的實作。
重複列舉範例程式:
IEnumerable<int> enumerable = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
foreach(var item in enumerable)
{
Console.WriteLine($"{item}");
}
foreach(var item in enumerable)
{
Console.WriteLine($"{item}");
}
先把 LINQ 的執行結果倒到另一個集合變數來儲存(俗稱的 materialize),當執行結果真正被儲存下來時,就可以被拿來重覆使用,不會有重複列舉的問題。
現在 C#:AI 時代的開發者修煉
C# 11 and .NET 7 – Modern Cross-Platform Development Fundamentals: Start building websites and services with ASP.NET Core 7