Dictionary<TKey, TValue> 是 .NET 最常用的雜湊表實作,提供 **O(1) 平均查詢/插入/刪除** 效能。本文系統整理從建立到進階用法的完整指南。
System.Collections.Genericnull 可作為 Value (參考型別),但**不可**作為 KeySortedDictionary 或 SortedList)
// 1. 預設容量
var dict = new Dictionary();
// 2. 指定初始容量 (避免擴容開銷)
var dict2 = new Dictionary(1000);
// 3. 指定比較器 (忽略大小寫、自定義相等性)
var dict3 = new Dictionary(StringComparer.OrdinalIgnoreCase);
// 4. 集合初始化器 (C# 6+)
var dict4 = new Dictionary
{
["C#"] = 1,
["Java"] = 2,
["Python"] = 3
};
// 5. 從現有字典複製
var dict5 = new Dictionary(dict4);
| 操作 | 方法 | 回傳/行為 | 例外情況 |
|---|---|---|---|
| 新增 | Add(key, value) | void | Key 重複 → ArgumentException |
| 新增/更新 | dict[key] = value | 索引器 | Key 不存在 → 新增;存在 → 覆蓋 |
| 嘗試新增 | TryAdd(key, value) (.NET Core 2.0+) | bool | 不拋例外,失敗回傳 false |
| 查詢 | dict[key] | TValue | Key 不存在 → KeyNotFoundException |
| 安全查詢 | TryGetValue(key, out value) | bool | 不拋例外,推薦高頻查詢使用 |
| 存在性檢查 | ContainsKey(key) / ContainsValue(value) | bool | Value 查詢為 O(n) |
| 移除 | Remove(key) | bool | 不存在回傳 false |
| 條件移除 | Remove(key, out value) (.NET Core 3.0+) | bool | 同時取得被移除值 |
| 清空 | Clear() | void | |
| 數量 | Count | int | O(1) |
var dict = new Dictionary { ["A"] = 1, ["B"] = 2, ["C"] = 3 };
// 1. KeyValuePair 結構體 (最常用,同時取得 Key/Value)
foreach (var kvp in dict)
{
Console.WriteLine($"Key={kvp.Key}, Value={kvp.Value}");
}
// 2. 僅遍歷 Keys
foreach (var key in dict.Keys)
{
Console.WriteLine(key);
}
// 3. 僅遍歷 Values
foreach (var value in dict.Values)
{
Console.WriteLine(value);
}
// 4. LINQ 查詢 (投影、過濾、排序)
var top3 = dict.OrderByDescending(kvp => kvp.Value).Take(3);
foreach (var kvp in top3) { ... }
// 5. 並行遍歷 (.NET 6+ Parallel.ForEach)
Parallel.ForEach(dict, kvp => { /* 執行緒安全需自行鎖定 */ });
// 選項 A: Tuple (C# 7+ ValueTuple 自動實作 Equals/GetHashCode)
var dict = new Dictionary<(int Year, int Month), double>();
dict[(2024, 1)] = 100.5;
// 選項 B: 自定義結構體 (需正確實作 Equals/GetHashCode)
struct CompositeKey { public int A; public string B; }
var dict2 = new Dictionary();
// 傳統寫法
if (!dict.TryGetValue(key, out var value))
{
value = ComputeExpensiveValue(key);
dict[key] = value;
}
// .NET 6+ GetOrAdd (需引用 System.Collections.Generic)
var value = dict.GetOrAdd(key, k => ComputeExpensiveValue(k));
var dict1 = new Dictionary { ["A"] = 1 };
var dict2 = new Dictionary { ["B"] = 2, ["A"] = 99 };
// 選項 A: 手動合併 (後者覆蓋)
foreach (var kvp in dict2) dict1[kvp.Key] = kvp.Value;
// 選項 B: LINQ (建立新字典)
var merged = dict1.Concat(dict2)
.GroupBy(kvp => kvp.Key)
.ToDictionary(g => g.Key, g => g.Last().Value); // 保留後者
var json = System.Text.Json.JsonSerializer.Serialize(dict);
// {"C#":1,"Java":2,"Python":3}
var restored = System.Text.Json.JsonSerializer.Deserialize>(json);
| 場景 | 建議 | 理由 |
|---|---|---|
| 已知大約資料筆數 | 建構子指定 capacity | 避免多次陣列擴容 (2倍增長) 與重新雜湊 |
| 高頻查詢 | 使用 TryGetValue 而非 ContainsKey + 索引器 | 單次雜湊查找 vs 兩次 |
| Key 為字串 | 考慮 StringComparer.Ordinal (預設) vs OrdinalIgnoreCase | Ordinal 最快,忽略大小寫需額外正規化 |
| Key 為結構體 | 確保正確實作 GetHashCode (組合所有欄位) | 差的雜湊函數導致碰撞,退化為 O(n) |
| 大量刪除後記憶體釋放 | 無內建 Trim,可 new Dictionary(oldDict) 重建 | 內部陣列不會自動縮減 |
| 多執行緒讀寫 | 使用 ConcurrentDictionary 或自行 ReaderWriterLockSlim | Dictionary 非執行緒安全 |
// ❌ 修改 Value 為參考型別時,直接修改物件內容不會觸發字典更新 (因為 Value 是參考)
dict["key"].SomeProperty = newValue; // 這沒問題,因為改的是物件內容
// ❌ 遍歷時修改集合
foreach (var kvp in dict) { if (kvp.Value < 0) dict.Remove(kvp.Key); }
// → InvalidOperationException: Collection was modified
// ✅ 正確:先收集要刪除的 Key
var toRemove = dict.Where(kvp => kvp.Value < 0).Select(kvp => kvp.Key).ToList();
foreach (var key in toRemove) dict.Remove(key);
// ❌ 以可變物件作 Key 並修改其內容
var listKey = new List { 1, 2 };
dict[listKey] = "value";
listKey.Add(3); // 修改 Key 內容 → 雜湊碼改變 → 找不到原項目!
// ✅ Key 必須不可變
Dictionary<TKey, TValue> 是高效查找的首選集合。掌握 TryGetValue、TryAdd、GetOrAdd 等現代 API,配合正確的**初始容量**、**比較器選擇**、**Key 不可變性**、**執行緒安全**策略,能發揮最大效能並避免常見 Bug。進階場景可考慮 ConcurrentDictionary、FrozenDictionary (.NET 8+ 唯讀高效能)、SortedDictionary 等變體。