clear() 应优先使用而非 new,因其仅重置状态、复用数组,避免扩容与gc压力;但需注意遍历时调用会崩溃、多线程须加锁,且容量残留可能造成内存浪费。

直接调用 Clear() 是绝大多数场景下的正确选择,不是“可以”,而是“应该”——除非你明确观察到容量严重浪费且长期闲置。
Clear() 不等于 new,它只重置状态,不释放底层数组
Clear() 内部只做两件事:把 _count 设为 0,把所有 _entries 中的键和值字段设为默认值(default(TKey) / default(TValue))。它完全不碰 _buckets 和 _entries 数组本身。
这意味着:
- 后续插入新元素时,无需重新分配内存、无需扩容,直接复用原有哈希表结构
- 如果字典曾膨胀到 100 万容量,
Clear()后仍是 100 万容量——这对高频重填是优势,但对长期空置是内存浪费 - 若字典里存的是大对象引用(如
byte[]、List<string></string>),这些对象在Clear()后若无其他引用,会立刻进入 GC 可回收状态
new Dictionary() 的真实开销远不止“多一次分配”
反复用 dict = new Dictionary<int string>();</int> 替换旧实例,问题不在“新建”本身,而在它触发的连锁反应:
- 原字典对象(含整个
_entries数组)变成垃圾,等待 GC 回收;高频循环中极易推高 Gen0 回收频率 - 新字典从初始容量(如 .NET 6+ 默认 0,首次 Add 才分配质数大小数组)开始,后续插入可能连续触发多次扩容(每次都要复制旧数据、重哈希)
- 实测对比:3 万次循环内填充 1 万个元素,用
Clear()耗时约 6 秒;用new耗时约 20 秒——差的主要是扩容和 GC 压力 - 若想保留容量又避免 GC 压力,可写成
dict = new Dictionary<int string>(dict.Capacity);</int>,但注意 .NET 5 及之前不支持传 0 容量,最小为 1
遍历中调用 Clear() 会直接崩溃
这是最容易被忽略的运行时陷阱。只要正在执行 foreach (var kv in dict) 或 dict.Keys.GetEnumerator(),此时调用 Clear() 就会抛出:
System.InvalidOperationException: Collection was modified; enumeration operation may not execute.
原因很直接:迭代器内部缓存了 _version 字段,Clear() 会递增它,导致版本校验失败。
安全替代方案只有两个:
- 确定不需要并发读写,就别在遍历时清空——先退出循环,再调
Clear() - 真需要边遍历边清理(比如按条件删部分项),用
dict.Keys.ToList()或dict.ToArray()拷贝键/项快照,再遍历快照去Remove()
多线程环境下 Clear() 必须加锁或换类型
Dictionary<tkey tvalue></tkey> 本身不是线程安全的。Clear() 不是原子操作:它要逐个归零 _entries、重置 _count、更新 _version。多个线程同时调用,或一边 Clear() 一边 TryGetValue(),极大概率出现 NullReferenceException 或返回错误结果。
简单加锁要注意:
- 锁对象不能是
dict自身(它可能被设为null) - 推荐用私有 readonly object 字段,如
private readonly object _dictLock = new object(); - .NET 6+ 的
ConcurrentDictionary<tkey tvalue>.Clear()</tkey>是真正线程安全的;.NET 5 及更早版本该方法只是“尽力而为”,仍需外部同步
容量残留和线程安全这两点,是生产环境里最常被漏掉的细节。别只盯着“清没清掉”,得看“谁在读、读得是否一致、内存还剩多少”。











