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

HashSet向上转换后集合操作不符合预期的原因与解决办法

问题原因与解决方案

核心问题:缺少正确的Equals实现

你遇到的问题根源在于没有重写Equals方法。HashSet和LINQ的集合操作(如Intersect、Contains)会同时依赖GetHashCode和Equals来判断对象是否相等:

  • 你已经正确重写了GetHashCode,基于Value计算哈希值,所以两个集合中同Value的对象哈希码一致。
  • 但默认使用的Object.Equals判断的是对象引用是否相同,而非逻辑值是否相等。比如setA中的DerivedTestNumber实例和setB中的TestNumberId实例,即使Value相同,也是不同的对象引用,Object.Equals会返回false;调用Contains(new TestNumberId(1))时,新实例和setA里的实例引用不同,同样会返回false。

解决方案:重写Equals并实现IEquatable

在基类BaseNumberClass<TVal>中重写Equals方法,基于Value判断逻辑相等,同时实现IEquatable<T>接口优化性能(避免装箱操作)。

修改后的完整代码

public void HashDerivedTest()
{
    var setA = new HashSet<DerivedTestNumber>
    {
        new DerivedTestNumber(1),
        new DerivedTestNumber(2),
        new DerivedTestNumber(3)
    }.Cast<TestNumberId>().ToHashSet();
    var setB = new HashSet<TestNumberId> { new TestNumberId(1), new TestNumberId(3), new TestNumberId(5) };

    var setAHashes = setA.Select(x => x.GetHashCode()).ToList();
    var setBHashes = setB.Select(x => x.GetHashCode()).ToList();

    var intersect = setA.Intersect(setB).ToSet();
    Assert.AreEqual(2, intersect.Count); // 现在会通过
    Assert.IsTrue(intersect.Contains(new TestNumberId(1))); // 现在会通过
    Assert.IsTrue(intersect.Contains(new TestNumberId(3))); // 现在会通过
}

private abstract class BaseNumberClass<TVal> : IEquatable<BaseNumberClass<TVal>>
    where TVal : IEquatable<TVal>, IComparable<TVal>
{
    public BaseNumberClass(TVal value) => this.Value = value;

    public TVal Value { get; }
    
    public override int GetHashCode() => this.Value.GetHashCode();

    public override bool Equals(object obj)
    {
        return Equals(obj as BaseNumberClass<TVal>);
    }

    public bool Equals(BaseNumberClass<TVal> other)
    {
        if (other is null)
            return false;
        if (ReferenceEquals(this, other))
            return true;
        return Value.Equals(other.Value);
    }

    public static bool operator ==(BaseNumberClass<TVal> left, BaseNumberClass<TVal> right)
    {
        return Equals(left, right);
    }

    public static bool operator !=(BaseNumberClass<TVal> left, BaseNumberClass<TVal> right)
    {
        return !Equals(left, right);
    }
}

private class TestNumberId : BaseNumberClass<long>
{
    public TestNumberId(long id)
        : base(id)
    {
    }
}

private class DerivedTestNumber : TestNumberId
{
    public DerivedTestNumber(long id)
        : base(id)
    {
    }
}

补充说明

  • 实现IEquatable<BaseNumberClass<TVal>>可避免Equals调用时的装箱操作,提升性能。
  • 重载==和!=运算符是可选操作,但能让代码中的相等判断更符合直觉。
  • 基类的Equals实现已能处理所有子类的逻辑相等判断,因为所有子类的相等性都基于Value字段。

内容的提问来源于stack exchange,提问作者Sybren Zwetsloot

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 03:45:14