You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在包含集合的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 17:05:08