现代C#项目中不建议直接使用Hashtable,除非维护老代码或有明确线程安全需求;其非泛型设计导致装箱拆箱开销大、类型转换风险高,Dictionary在性能、类型安全和内存效率上全面优于它。

现代 C# 项目里,不建议直接用 Hashtable,除非你在维护老代码或有明确线程安全需求。 它的非泛型设计、装箱拆箱开销、运行时类型转换风险,已经让 Dictionary<tkey tvalue></tkey> 成为更优解。但理解它,能帮你读懂遗留系统、排查类型转换异常、甚至看懂 ConcurrentDictionary 的演进逻辑。
为什么 Hashtable.Add() 会抛出 ArgumentException
常见错误是往同一个 Hashtable 中重复添加相同 key:
ht.Add("id", 1001);
ht.Add("id", 1002); // ⚠️ 这里直接炸:ArgumentException: An entry with the same key already exists.
原因很简单:Hashtable 要求 key 唯一且不可变。它内部用 key.GetHashCode() 算哈希值,再映射到桶(bucket)位置;重复 key 意味着哈希冲突处理失败,直接拒绝插入。
- 检查是否误用了
Add()而非[key] = value(后者是覆盖行为) - 注意字符串 key 的大小写敏感性:
"Name"和"name"是两个不同 key - 自定义对象作 key 时,必须重写
GetHashCode()和Equals(),否则默认引用比较会导致“看似相同却无法命中”
取值时强制转换 (string)ht["name"] 总报 InvalidCastException 怎么办
这是 Hashtable 最典型的运行时陷阱:它存的是 object,取出来必须手动转回原类型。一旦类型不匹配,就崩。
比如你存了 ht["count"] = 42;(int 装箱成 object),但取的时候写 string s = (string)ht["count"]; —— 直接抛 InvalidCastException。
- 用
ContainsKey("key")先判断 key 是否存在,避免null引发NullReferenceException - 用
as安全转换代替强制转换:string? name = ht["name"] as string;,失败返回null而非异常 - 对值类型(
int、bool等),优先用可空类型 +as或Convert.ToInt32()防装箱失败
Hashtable.Synchronized() 真的安全吗
Hashtable.Synchronized(new Hashtable()) 返回一个线程安全包装器,但它只保证单个操作原子性,比如 Add() 或 ContainsKey() 不会被并发打断。但它不保证复合操作的原子性。
例如下面这段代码仍是线程不安全的:
if (!syncTable.ContainsKey("status")) {
syncTable["status"] = "running"; // ⚠️ 两步:查 + 写,中间可能被其他线程插入同 key
}
- 这种“先查后写”场景,要用
lock手动同步,或改用ConcurrentDictionary -
Synchronized()包装器性能较差,每次调用都加锁,高并发下成为瓶颈 - 现代替代方案是
ConcurrentDictionary<string object></string>,它支持GetOrAdd()、AddOrUpdate()等原子复合操作
Hashtable 和 Dictionary 的性能差在哪
差距主要来自装箱/拆箱和类型检查:
- 往
Hashtable存int:触发装箱 → 新建object实例 → 占内存、GC 压力大 - 从
Hashtable取int:触发拆箱 → 类型校验 + 复制值 → 多一次运行时开销 -
Dictionary<string int></string>全程在栈/堆上操作原生类型,无装箱,编译器还能做泛型特化优化 - 实测:百万级整数读写,
Dictionary比Hashtable快 30%~50%,内存占用低 20%+
真正需要关注的不是“怎么用 Hashtable 更快”,而是“有没有必要继续用它”。除非你在对接 .NET Framework 1.1 时代的 COM 组件,或者调试一段无法修改的旧 ASP.NET Web Forms 代码,否则请直接切到 Dictionary 或 ConcurrentDictionary。










