foreach 遍历 Dictionary 最安全,天然避免并发修改异常和索引越界;但禁止循环内删除元素,需只读遍历或批量更新,注意装箱、顺序、空引用及线程安全问题。

用 foreach 遍历 Dictionary 最安全
直接用 foreach 是绝大多数场景下的首选,它天然避免了并发修改异常和索引越界问题。Dictionary 的枚举器保证在遍历时结构不被修改的前提下,能稳定访问所有键值对。
常见错误是试图在 foreach 循环里删元素,比如写 dict.Remove(k) —— 这会立刻抛出 InvalidOperationException: Collection was modified。
- 只读遍历?用
foreach (var kvp in dict),kvp.Key和kvp.Value可直接取值 - 需要同时改值?先收集要更新的键,循环外批量操作;或改用
for+dict.Keys.ToArray()(注意内存开销) - 想按插入顺序遍历?.NET 6+ 的
Dictionary本身不保证顺序;需要顺序请用SortedDictionary或手动维护List>
用 Keys 或 Values 属性单独遍历要注意装箱和性能
dict.Keys 和 dict.Values 返回的是视图(view),不是新集合,但它们的类型取决于泛型参数:值类型 Value 在遍历时若被当作 object 使用(比如存进 ArrayList 或反射调用),会触发装箱。
典型坑:写 foreach (object v in dict.Values) 对 Dictionary,每个 int 都装箱一次。
- 明确类型就写
foreach (int v in dict.Values),编译器能绕过装箱 -
dict.Keys同理,string键没问题,但struct键也要注意类型声明 - 不要为了“简洁”把
dict.Keys.ToList()当遍历起点——这额外分配内存,且失去延迟执行优势
用 LINQ 查询 Dictionary 要小心延迟执行和空引用
Where、FirstOrDefault 这些方法本身不执行,直到你调用 ToList()、Count() 或进入 foreach。但很多人忽略 dict 本身为 null 时,dict.Where(...) 会直接崩,不是返回空序列。
另一个隐形坑:dict.FirstOrDefault(x => x.Key == target) 返回的是 KeyValuePair,如果 TValue 是可空引用类型(如 string?),解构时别假设 .Value 一定非空。
- 判空优先:用
dict?.Where(...)或提前if (dict is null) return; - 查单个值更推荐
dict.TryGetValue(key, out var value),比FirstOrDefault(x => x.Key == key)快一个数量级,且语义清晰 - 需要投影再遍历?写
dict.Select(x => new { x.Key, Length = x.Value.Length })比先转ToList()再处理更省内存
多线程读写 Dictionary 必须加锁或换类型
原生 Dictionary 不是线程安全的。即使只是“多个线程只读”,.NET 旧版本(如 .NET Framework 4.7.2 之前)在扩容时也可能导致读线程崩溃;.NET Core / 5+ 改进了,但规范仍要求:只要有一个线程在写,其他线程就不能读。
最常被忽略的点:你以为自己没写,其实 ContainsKey + dict[key] = value 是两步,中间可能被其他线程插入同 key,导致覆盖或异常。
- 纯读多写少?用
ConcurrentDictionary,它的GetOrAdd和AddOrUpdate是原子的 - 已有代码难改?至少把读写逻辑包在
lock (dictLock)里,锁对象别用this或dict本身 - 千万别用
dict.AsParallel()遍历——并行枚举器底层仍依赖非线程安全的枚举逻辑
Dictionary 看似简单,但键的哈希稳定性、枚举器生命周期、线程模型这几处,稍不注意就会在压测或高并发时露馅。尤其是 TryGetValue 和 ConcurrentDictionary 的适用边界,很多人凭直觉选错。











