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

HashSet 初始化和基本添加操作
直接用 new HashSet 创建即可,不需要手动去重逻辑。它内部基于哈希表实现,插入、查找平均时间复杂度是 O(1)。
常见错误:用 List + Contains 手动判断再添加,性能差且易漏判(尤其并发场景)。
- 添加重复元素时,
Add()返回false,原集合不变 - 支持泛型约束,比如
HashSet、HashSet,不支持裸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 本身不是线程安全的。多线程同时调用 Add 或 Contains 可能导致异常或数据损坏。
- 简单场景:用
lock包裹操作块,注意锁粒度别太大 - 高并发场景:改用
ConcurrentHashSet(需 NuGet 安装System.Collections.Concurrent扩展包),或ConcurrentDictionary模拟(key 存元素,value 固定为null) - 陷阱:用
foreach遍历时修改集合(如边遍历边Remove)会抛出InvalidOperationException
HashSet,也没有 ConcurrentHashSet 类型(除非你装了第三方扩展包)。很多人以为 ConcurrentDictionary 有对应集合,其实没有——得自己封装或接受折中方案。**










