商城首页欢迎来到中国正版软件门户

您的位置:首页 >C#怎么使用Dictionary字典 C#如何用Dictionary存储键值对实现高效查找和遍历【基础】

C#怎么使用Dictionary字典 C#如何用Dictionary存储键值对实现高效查找和遍历【基础】

  发布于2026-05-02 阅读(0)

扫一扫,手机访问

C# Dictionary必须指定泛型类型,否则编译失败;键需不可变且重写GetHashCode和Equals;取值应优先用TryGetValue避免KeyNotFoundException;遍历用foreach(var kvp in dict)而非Keys/Values分离;多线程须用ConcurrentDictionary或加锁。

C#怎么使用Dictionary字典 C#如何用Dictionary存储键值对实现高效查找和遍历【基础】

Dictionary 初始化时必须指定泛型类型

直接写 new Dictionary() 是行不通的,编译器会直接报错。C# 的 Dictionary 是个强类型的家伙,TKeyTValue 这两个泛型参数一个都不能少。比如,你想建立一个从字符串ID到用户对象的映射,正确的写法是 new Dictionary()。可别图省事写成 new Dictionary(),或者用 new Dictionary() 来蒙混过关——后者虽然能通过编译,但不仅失去了类型安全,还会引入不必要的装箱开销,得不偿失。

常见的错误提示是编译错误 CS0305(使用了未指定泛型参数的类型),或者在运行时冷不丁地抛出一个 InvalidCastException

  • 键类型的选择有讲究:优先使用不可变、且已经正确重写了 GetHashCode()Equals() 方法的类型,像 stringintGuid 这些都是“模范键”。
  • 自定义类当键?务必重写那两个方法:如果你用自己的类作为键,却忘了重写 GetHashCode()Equals(),那么查找操作很可能会失效,因为字典默认使用引用相等来判断。
  • 警惕可变键:避免使用内容可变的引用类型(比如一个没有重写相关方法的 List)作为键。因为一旦键对象的内容发生变化,其哈希码也可能改变,导致你再也找不到之前存入的数据。

添加和查找键值对必须注意 KeyNotFoundException

通过索引器直接取值,比如 dict[“abc”],是很多新手踩坑的地方。如果键“abc”不存在,它可不会友好地返回 null 或默认值,而是会直接抛出一个 KeyNotFoundException 异常。这在不确定键是否存在的场景下,尤其危险。

那么,正确的打开方式有哪些呢?主要分三类:

  • 场景一:百分百确定键存在。那就直接用 dict[key],简单直接,性能也最好。但前提是你的逻辑必须保证这个键已经被添加了。
  • 场景二:不确定键是否存在,但需要获取值。这是最推荐的做法:使用 dict.TryGetValue(key, out value)。它返回一个布尔值告诉你查找是否成功,并通过 out 参数返回找到的值。不抛异常,性能也优化过,一举两得。
  • 场景三:只关心键是否存在,不关心值。可以用 dict.ContainsKey(key)。不过要注意,它通常比 TryGetValue 略慢一点,因为内部可能多了一次哈希查找。除非你真的不需要那个值,否则 TryGetValue 通常是更优选择。

来看一个典型的用法示例:

if (users.TryGetValue(“U1001”, out var user))
{
    Console.WriteLine(user.Name);
}
else
{
    Console.WriteLine(“用户不存在”);
}

遍历 Dictionary 推荐用 foreach + KeyValuePair 而非 Keys/Values 分离

当需要同时遍历键和值时,有一种写法需要避免:foreach (var key in dict.Keys) { var val = dict[key]; ... }。问题在于,这会导致对同一个键进行两次哈希查找(一次在 Keys 集合中,一次通过索引器取值),性能有损耗。在并发修改的场景下,还可能引发错误。

更优雅高效的做法是直接遍历 Dictionary 本身,它会返回一系列的 KeyValuePair 结构体:

  • 标准写法foreach (var kvp in dict),然后通过 kvp.Keykvp.Value 访问。这是最清晰、最常用的方式。
  • 解构写法(C# 7及以上):如果想让代码更简洁,可以使用元组解构语法:foreach (var (id, user) in dict)。这要求键和值的类型是明确的,看起来一目了然。
  • 需要避免的写法:不要为了遍历而特意调用 dict.Keys.ToList()。这会产生一个不必要的中间列表,分配额外内存,并且丢失了原始字典的结构语义。

Dictionary 不是线程安全的,多线程读写必须加锁或换 ConcurrentDictionary

这是一个至关重要的安全提示:标准的 Dictionary 并非线程安全。当多个线程同时执行添加、删除甚至某些读取操作时,可能会导致其内部数据结构损坏,结果就是抛出 InvalidOperationException 异常,或者返回错误的数据。即便是“读多写少”的场景,只要存在任何一个写操作,就不能毫无保护地使用普通 Dictionary

别以为“我只读不写就没事”。要知道,Dictionary 在容量不足需要扩容时,会在内部重建数组,这个过程中并发读取也可能导致问题。

应对多线程访问,主要有两种主流策略:

  • 手动同步(加锁):使用 lock 语句包裹所有对字典的读写操作。锁对象建议使用一个私有的、只读的字段,避免直接锁 this 或字典实例本身,以减少死锁风险。
  • 使用线程安全集合:直接切换到 ConcurrentDictionary。它在内部采用了更精细的锁机制(如分段锁),使得读操作通常可以无锁进行,写操作的锁粒度也更小,性能更好。但需要注意,其 GetOrAddAddOrUpdate 等方法接收的委托(工厂函数)可能是延迟执行的,并且可能被调用多次,因此不要在委托内编写带有副作用的逻辑。
本文转载于:https://www.php.cn/faq/2316354.html 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注