结论:一律改用 trygetvalue,它比 containskey + 索引器更快(单次哈希查找 vs 两次)、更安全、更简洁,且是微软 ca1854 推荐写法;正确用法需检查返回值、用 out 接收、不预初始化。

直接说结论:别用 ContainsKey + 索引器,一律改用 TryGetValue —— 它更快、更安全、更简洁,而且是微软静态分析器(CA1854)明确推荐的写法。
为什么 TryGetValue 比 ContainsKey + [key] 更快
因为 ContainsKey 和索引器 [key] 各自做一次哈希查找,加起来是两次;而 TryGetValue 只查一次,就把“是否存在”和“值是多少”两个结果都拿回来了。
实测数据(1000 万次循环):
-
ContainsKey+[key]:约 46 ns/次 -
TryGetValue:约 30 ns/次
差 16 ns 看似不多,但高频访问(如日志聚合、配置解析、路由匹配)下会累积成可观开销。更重要的是,它不是“可能快”,而是“必然少一次查找”——这是底层哈希表实现决定的,不依赖运行时优化。
TryGetValue 的标准写法与常见错误
正确写法必须同时满足三个条件:检查返回值、用 out 接收值、不假设 value 已初始化。
- ✅ 正确:
if (dict.TryGetValue("name", out int age)) { /* age 已赋值 */ } - ❌ 错误:
dict.TryGetValue("name", out int age); Console.WriteLine(age);—— 忽略返回值,age可能是默认值(如 0),且逻辑上已失效 - ❌ 危险:
int age = 0; dict.TryGetValue("name", out age);—— 多此一举初始化,out参数不需要预先赋值,编译器会强制覆盖
对于引用类型(如 string),未找到键时 out 参数为 null;对于值类型(如 int),为对应类型的 default(default(int) 是 0,不是“未定义”)。
用 out var 简化代码,但要注意作用域
C# 7.0 起支持 out var,让变量声明和接收一步到位:
if (config.TryGetValue("timeout", out var timeoutMs))
{
// timeoutMs 类型自动推断为 int(假设字典是 Dictionary<string int>)
ProcessWithTimeout(timeoutMs);
}
</string>
注意:timeoutMs 只在 if 块内有效。如果需要在 else 或后续语句中使用,必须提前声明:
- 需要跨分支?写
int timeoutMs;再out timeoutMs - 只在
if里用?out var最干净
别为了省一个类型名,把变量生命周期搞乱 —— 尤其在长方法里,容易误读变量是否已被赋值。
线程安全场景下不能直接用 TryGetValue
Dictionary<tkey tvalue></tkey> 本身不是线程安全的。即使你用 TryGetValue,在多线程并发读写时仍可能抛出 InvalidOperationException(如结构被修改中)。
- 纯读?可配合
ReaderWriterLockSlim或用ConcurrentDictionary -
ConcurrentDictionary也提供TryGetValue,行为一致,且线程安全 —— 但注意它内部有锁或无锁策略,吞吐量和争用表现不同 - 别幻想“
TryGetValue原子 = 整个字典操作安全”——它只保证单次查找原子,不保护后续对value的使用逻辑
最常被忽略的一点:即使用了 ConcurrentDictionary.TryGetValue,如果你紧接着基于返回的 value 做修改(比如 list.Add(...)),那个 list 本身未必线程安全 —— TryGetValue 解决不了整条业务链路的并发问题。











