在IEqualityComparer的实现中,GetHashCode和Equals有什么关系?
在IEqualityComparer的实现中,GetHashCode和Equals有什么关系?
这个问题已经有答案了:
我有一个从类B继承并实现IEqualityComparer
的类A。这意味着类A提供了自己的Equals和GetHashCode方法的实现。目前还好。
问题在于我不明白为什么代码会以以下方式运行:
如果A的GetHashCode实现返回this.GetHashCode()
而不是obj.GetHashCode()
,那么调试器只会到达A的Equals实现断点,其中“obj”是GetHashCode签名定义的参数(在我的情况下是类型为A的变量)。
直观地,我认为应该返回我接收到的对象的哈希码,但这样做会使编译器忽略实例的Equals实现。
为什么会出现这种情况?
代码演示:
public class A : B, IEqualityComparer { public bool Equals(A x, A y) { //my implementation... } public int GetHashCode(A obj) { //return obj.GetHashCode(); -> this makes my Equals implementation above be ignored! Why? return this.GetHashCode(); -> my Equals implementation is used } }
实现 IEqualityComparer
不会 覆盖
基本的 GetHashCode
和 Equals
实现。
实现 IEqualityComparer
允许你提供一个实现者的实例作为 T
的相等比较器。这是许多 Linq 扩展和泛型集合构造函数的常见参数。
覆盖 Equals
和 GetHashCode
影响类的实例测试相等的方式。利用调用 Equals
和 GetHashCode
的其他实现,如基本的 =
和 !=
运算符,以及你不提供替代 IEqualityComparer
的 Linq 扩展和泛型集合构造函数。
这些概念类似,但 服务于不同的目的,它们不是部分互换的。
下面通过一个例子来进一步说明,
public class A { public string Value1 { get; set; } public int Value2 { get; set; } public override int GetHashCode() { unchecked { int hash = 17; hash = (hash * 23) + StringComparer.Ordinal.GetHashCode(this.Value1); hash = (hash * 23) + this.Value2; return hash; } } public override bool Equals(object obj) { var a = obj as A; if (a == null) { return false; } if (a.Value2 != this.Value2) { return false; } return StringComparer.Ordinal.Equals( a.Value1, this.Value1); } }
这个 A
的实现正确地覆盖了 Equals
和 GetHashCode
,这个改变足以确保在调用 Linq 扩展
var distinct = aSequneceOfA.Distinct();
之后,distinct
不会包含任何同时具有相同的 Value2
和按序比较的 Value1
的实例。没有其他接口实现是必要的。
现在,假设在某些情况下,我不满意对 Value1
的按序比较,也许我需要一些不区分大小写的情况。我可以实现一个新的相等比较器。
public class AComparerInsensitive : IEqualityComparer { public bool Equals(A x, A y) { if (x == null) { return y == null; } if (y == null) { return false; } if (x.Value2 != y.Value2) { return false; } return StringComparer.CurrentCultureIgnoreCase.Equals( x.Value1, y.Value1) } public int GetHashCode(A a) { if (a == null) { return 0; } unchecked { int hash = 17; hash = (hash * 23) + StringComparer.CurrentCultureIgnoreCase.GetHashCode( a.Value1); hash = (hash * 23) + a.Value2; return hash; } } }
这将允许我调用 Distinct
的替代重载,
var insensitivelyDistinct = aSequneceOfA.Distinct( new AComparerInsensitive());
这个重载的 Distinct
忽略了 A
的覆盖 Equals
和 GetHashCode
,并使用 AComparerInsensitive
执行比较。
听起来你使用的是错误的接口。 IEqualityComparer<>
通常用于比较其他类型的实例的类。
你的类型应该简单地实现 IEquatable
并且重写 Equals(object)
和 GetHashCode()
。注重签名。
像这样:
public class A : B, IEquatable { public bool Equals(A other) { if (other == null || GetType() != other.GetType()) return false; //your implementation } public override bool Equals(object obj) { return Equals(obj as A); } public override int GetHashCode() { //your implementation } }
然后你可以做 someEnumerableOfA.Distinct()
这样的事情,Linq 方法会使用你的实现。
另一种选择是:
public class A : B // no interfaces { } public class AEqualComparer : IEqualityComparer { public bool Equals(A x, A y) { //your implementation } public int GetHashCode(A x) { //your implementation } }
使用这个选项需要 someEnumerableOfA.Distinct(new AEqualComparer())
。