C# 哈希集合HashSet使用方法 C#如何存储不重复的元素

7次阅读

HashSet用new HashSet()初始化即可,内部哈希表实现O(1)增查;存自定义类须重写GetHashCode和Equals或传IEqualityComparer;不保证顺序,线程需手动同步。

C# 哈希集合HashSet使用方法 C#如何存储不重复的元素

HashSet 初始化和基本添加操作

直接用 new HashSet() 创建即可,不需要手动去重逻辑。它内部基于哈希表实现,插入、查找平均时间复杂度是 O(1)。

常见错误:用 List + Contains 手动判断再添加,性能差且易漏判(尤其并发场景)。

  • 添加重复元素时,Add() 返回 false,原集合不变
  • 支持泛型约束,比如 HashSetHashSet,不支持裸 HashSet
  • 若存自定义类,必须重写 GetHashCode()Equals(),否则默认引用比较,相同内容对象仍被当作不同元素

如何正确处理自定义类型去重

默认情况下,HashSet 把两个字段完全相同的 Person 实例视为不同元素——因为没指定比较逻辑。

解决方法是传入实现了 IEqualityComparer 的比较器,或让类型自身实现该接口

  • 推荐方式:在构造时传入比较器,如 new HashSet(new PersonComparer())
  • 更简洁做法:让 Person 类实现 IEquatable 并重写 GetHashCode()Equals(Person)
  • 注意:仅重写 Equals(Object) 不够,HashSet 优先调用泛型 Equals(T)

HashSet 与 List/Distinct() 的实际取舍

不是所有“去重需求”都该用 HashSet。它无序、不可索引、不保证插入顺序(.net 5+ 保持插入顺序,但属实现细节,不应依赖)。

典型误用场景:先去重再按原顺序遍历,却选了 HashSet 导致顺序错乱。

  • 需要保留首次出现顺序 → 用 List.Distinct()(配合 IEqualityComparer),或手写带字典缓存的去重循环
  • 高频查询/插入且不要求顺序 → HashSet 是最优解
  • 要同时支持快速查重 + 按索引访问 → 考虑 List + HashSet 双结构维护(空间换时间)

线程安全与常见陷阱

HashSet 本身不是线程安全的。多线程同时调用 AddContains 可能导致异常或数据损坏。

  • 简单场景:用 lock 包裹操作块,注意锁粒度别太大
  • 高并发场景:改用 ConcurrentHashSet(需 NuGet 安装 System.Collections.Concurrent 扩展包),或 ConcurrentDictionary 模拟(key 存元素,value 固定为 NULL
  • 陷阱:用 foreach 遍历时修改集合(如边遍历边 Remove)会抛出 InvalidOperationException

真正容易被忽略的是:**.NET 中没有内置的线程安全版 HashSet,也没有 ConcurrentHashSet 类型(除非你装了第三方扩展包)。很多人以为 ConcurrentDictionary 有对应集合,其实没有——得自己封装或接受折中方案。**

text=ZqhQzanResources