在IEqualityComparer的实现中,GetHashCode和Equals有什么关系?

11 浏览
0 Comments

在IEqualityComparer的实现中,GetHashCode和Equals有什么关系?

这个问题已经有答案了:

当重写Equals方法时为什么重写GetHashCode方法很重要?

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
    }
}

admin 更改状态以发布 2023年5月21日
0
0 Comments

实现 IEqualityComparer 不会 覆盖 基本的 GetHashCodeEquals 实现。

实现 IEqualityComparer 允许你提供一个实现者的实例作为 T 的相等比较器。这是许多 Linq 扩展和泛型集合构造函数的常见参数。

覆盖 EqualsGetHashCode 影响类的实例测试相等的方式。利用调用 EqualsGetHashCode 的其他实现,如基本的 =!= 运算符,以及你不提供替代 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 的实现正确地覆盖了 EqualsGetHashCode,这个改变足以确保在调用 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 的覆盖 EqualsGetHashCode,并使用 AComparerInsensitive 执行比较。

0