如何在包含集合的C# Record中实现HashSet唯一性校验?
在包含嵌套集合的C# Record中确保HashSet唯一性的解决办法
问题原因
C# Record默认的结构相等性对引用类型(比如HashSet<int>这类集合)采用的是引用地址比较,而非集合内容比较。哪怕两个Record对象的普通字段完全一致,只要嵌套集合是不同的实例(哪怕内容为空或完全相同),Record会判定这两个对象不相等,HashSet也就会把它们当成独立元素存储,导致去重失效。
解决方案
方案一:给HashSet传入自定义相等比较器
通过实现IEqualityComparer<Person>,自定义HashSet判断元素相等的逻辑,同时比较普通字段和集合内容。
public class PersonEqualityComparer : IEqualityComparer<Person> { public bool Equals(Person? x, Person? y) { if (ReferenceEquals(x, y)) return true; if (x is null || y is null) return false; // 比较FirstName字段 if (!string.Equals(x.FirstName, y.FirstName, StringComparison.Ordinal)) return false; // 比较集合内容,用SetEquals判断两个集合元素是否完全一致 return x.FavouriteNumbers.SetEquals(y.FavouriteNumbers); } public int GetHashCode(Person obj) { var hash = new HashCode(); hash.Add(obj.FirstName, StringComparer.Ordinal); // 遍历集合元素,将每个元素的哈希值加入,确保集合内容不同时哈希值也不同 foreach (var num in obj.FavouriteNumbers) { hash.Add(num); } return hash.ToHashCode(); } }
使用时,把自定义比较器传给HashSet的构造函数:
HashSet<Person> uniquePeople = new(new PersonEqualityComparer()); uniquePeople.Add(new Person { FirstName = "John" }); uniquePeople.Add(new Person { FirstName = "John" }); Assert.That(uniquePeople.Count == 1); // 断言通过
方案二:改用值语义的不可变集合
将嵌套集合类型换成ImmutableHashSet<int>(需引用System.Collections.Immutable包),不可变集合的相等性基于内容而非引用,这样Record的默认结构相等性就能正确识别内容相同的集合。
修改后的Record:
public record Person { public string FirstName { get; set; } public ImmutableHashSet<int> FavouriteNumbers { get; set; } = ImmutableHashSet<int>.Empty; }
注意不可变集合无法直接修改,需通过生成新实例的方式变更内容:
var person = new Person { FirstName = "John" }; // 添加元素会生成新的ImmutableHashSet实例 var updatedPerson = person with { FavouriteNumbers = person.FavouriteNumbers.Add(5) };
这种方式无需自定义比较器,但需要适配不可变集合的使用逻辑。
方案三:重写Record的Equals和GetHashCode(不推荐)
手动重写Record的Equals和GetHashCode方法,改变其默认的相等性判断逻辑。但这种方式会影响Record在所有场景下的相等性行为,可能引发意外问题,仅在必须全局修改相等性逻辑时使用。
public record Person { public string FirstName { get; set; } public HashSet<int> FavouriteNumbers { get; set; } = new(); public virtual bool Equals(Person? other) { if (other is null) return false; return string.Equals(FirstName, other.FirstName, StringComparison.Ordinal) && FavouriteNumbers.SetEquals(other.FavouriteNumbers); } public override int GetHashCode() { var hash = new HashCode(); hash.Add(FirstName, StringComparer.Ordinal); foreach (var num in FavouriteNumbers) { hash.Add(num); } return hash.ToHashCode(); } }
总结
推荐优先使用方案一,它仅作用于目标HashSet,不会改变Record的全局相等性逻辑;如果项目本身就采用不可变集合架构,方案二会更简洁。
内容的提问来源于stack exchange,提问作者mulllhausen
相关产品推荐
相关产品推荐

