在 C# 開發的世界裡,有些方法就像隱藏的寶石,平時不顯山露水,但一旦掌握,就能讓代碼瞬間變得優雅高效。ToLookup 就是這樣一個被低估的 LINQ 擴展方法。它不會大聲呼喚你使用,但資深開發者都知道:遇到分組查詢、快速查找場景,它往往是最佳拍檔。
我們先從一個經典場景說起。假設有一組產品資料,包含 ID、類別與價格:
public sealed class Product {
public int Id { get; set; }
public string Category { get; set; }
public double Value { get; set; }
public override string ToString() => $"[{Id}: {Category} - {Value}]";
}
若要按類別分組顯示,GroupBy 當然輕鬆搞定:
foreach (var group in products.GroupBy(p => p.Category)) {
Console.WriteLine(group.Key);
foreach (var item in group) Console.WriteLine($"\t{item}");
}
但 GroupBy 採用延遲執行。這意味著分組實際發生在你遍歷集合的那一刻,而非調用 GroupBy 時。這帶來一個隱患:若在分組後、遍歷前修改了原始集合,結果會跟著變。
var groups = products.GroupBy(p => p.Category);
// 此時刪除所有 Garden 類產品
products.RemoveAll(p => p.Category == "Garden");
// 遍歷 groups 時,Garden 已消失!
foreach (var group in groups) { ... }
為了「凍結」分組結果,開發者常轉用 ToDictionary,但這需要額外處理鍵值結構,代碼變得笨重。
ToLookup 專為解決此痛點而生。它立即執行,產出一個 ILookup<TKey, TElement>—— 類似字典,但不可變,且一個鍵對應多個值(一對多映射)。一旦創建,內容固定不變,完全免受原集合後續修改影響。
var productsByCategory = products.ToLookup(p => p.Category);
// 像遍歷字典一樣遍歷
foreach (var group in productsByCategory) {
Console.WriteLine(group.Key); // 鍵:類別名
foreach (var item in group) { // 值:該類別下的產品集合
Console.WriteLine($"\t{item}");
}
}
更方便的是,它支援索引器語法直接取值:
// 直接獲取 Electronics 類別的所有產品
foreach (var item in productsByCategory["Electronics"]) {
Console.WriteLine(item);
}
若鍵不存在,返回空序列而非拋出異常,極其安全。
| 特性 | GroupBy | ToLookup |
|---|---|---|
| 執行模式 | 延遲 | 立即 |
| 結果可變性 | 受原集合影響 | 不可變快照 |
| 返回類型 | IEnumerable<IGrouping> | ILookup<TKey, TElement> |
| 索引器存取 | 不支援 | 支援 |
| 鍵不存在行為 | 無對應群組 | 返回空序列 |
ToLookup 是 LINQ 工具箱裡的「瑞士軍刀」:它將分組與快速查找合二為一,以不可變快照消除副作用,讓代碼更簡潔、更安全。下次當你寫 GroupBy(...).ToDictionary(...) 時,不妨停下想想——ToLookup 或許才是你真正需要的。