dictionary 使用需规避踩坑:初始化优先集合表达式,预估容量用 new dictionary(n),键禁用 null 且须不可变,add() 严格插入而索引器赋值覆盖,取值必用 trygetvalue() 防异常,遍历时修改需先缓存键再批量删,多线程用 concurrentdictionary。

Dictionary 不是“用不用”的问题,而是“怎么用才不踩坑”。它在 C# 里性能好、语义清,但新手常因忽略键的唯一性、空值处理或异常路径而线上报错。
初始化时用集合表达式还是 new()?
优先用集合表达式({ {"key", "value"}, ["k"] = "v" }),语义直接、可读性强,且编译期就能捕获重复键(比如两个 {"id", "a"} 和 {"id", "b"} 同时出现会报错)。但要注意:{"key", "value"} 和 ["key"] = "value" 是两种语法,前者靠 Add() 实现,后者靠索引器,行为一致;不过混合使用时容易混淆顺序和意图。
如果需要预估容量(比如确定要塞 5000 条数据),用 new Dictionary<string int>(5000)</string> 可减少哈希表扩容次数,避免多次 rehash 带来的内存抖动和性能损耗。
- 不要用无泛型
Dictionary(即System.Collections.Dictionary),类型不安全,装箱拆箱开销大 - 键类型尽量选不可变、重写了
GetHashCode()和Equals()的类型(string、int安全;自定义类需手动实现) - 避免用
null作键——Dictionary<string int></string>允许键为null吗?不。会抛ArgumentNullException
添加元素:Add() 还是索引器 []?
Add() 是严格插入:键已存在 → 抛 ArgumentException;dict["k"] = v 是“赋值语义”:键存在就覆盖,不存在就新增。生产代码里,除非你明确想拦截重复键(比如校验配置唯一性),否则别用 Add()。
常见错误现象:ArgumentException: An item with the same key has already been added. 就是误把 Add() 当成“安全插入”用了。
- 想“有则更新、无则新增”,直接用
dict[key] = value - 想“只新增、拒绝覆盖”,用
Add(),但必须包 try/catch 或先ContainsKey() - 想“原子性判断并赋值”,用
dict.TryAdd(key, value)(.NET 5+ 才有)
取值时为什么总抛 KeyNotFoundException?
因为直接写 dict["missing"] 时,键不存在就炸,不给商量余地。这不是设计缺陷,是明确告诉你“这里必须存在”。但多数业务逻辑里,键可能不存在(比如查用户偏好设置,没设过就该走默认)。
正确姿势永远是 TryGetValue():
if (dict.TryGetValue("theme", out string theme)) {
Apply(theme);
} else {
Apply("light"); // fallback
}
-
TryGetValue()比ContainsKey() + dict[key]快一倍——少一次哈希查找 - 别用
dict.ContainsKey(k) && dict[k],这是典型双查陷阱 - 如果真要“取不到就返回默认值”,.NET 7+ 支持
dict.GetValueOrDefault("k", "default"),但注意它不区分“键不存在”和“键存在但值为 default(TValue)”
遍历时修改字典会怎样?
直接 foreach (var kvp in dict) { dict.Remove(kvp.Key); } → InvalidOperationException: Collection was modified。这是 .NET 集合的通用保护机制,不是 Dictionary 特有。
要安全删,得先收集要删的键,再批量删:
var keysToRemove = dict.Where(kvp => kvp.Value == null).Select(kvp => kvp.Key).ToArray();
foreach (var key in keysToRemove) {
dict.Remove(key);
}
- 别在 foreach 中调
Add()、Remove()、Clear() - 如果只是读值,
dict.Values或dict.Keys是只读视图,可以放心遍历 - 多线程场景下,
Dictionary本身不线程安全;要用ConcurrentDictionary替代
真正难的不是语法,是理解哈希表的本质:它快,是因为牺牲了顺序和容错;它稳,是因为你主动控制了键的生命周期和存在性判断路径。漏掉 TryGetValue 或硬写 [],往往就是线上 KeyNotFoundException 的起点。











