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
相关产品推荐
相关产品推荐

