IEqualityComparer实现中GetHashCode和Equals之间的关系是什么? [重复]

这个问题在这里已有答案:

我有一个继承自B类并实现 IEqualityComparer<A> 的A类 . 这意味着A类提供了自己的Equals和GetHashCode方法实现 . 到现在为止还挺好 . 问题是我不明白为什么代码的行为方式如下:

调试器只会到达A 's Equals implementation breakpoint if A'的GetHashCode实现返回 this.GetHashCode() 而不是 obj.GetHashCode() ,其中"obj"是GetHashCode签名定义的参数(在我的例子中是A类型的变量) .

直觉上,我认为我应该返回我收到的对象的哈希码,但这样做会使编译器忽略实例的Equals实现 .

为什么会这样?

代码演示:

public class A : B, IEqualityComparer<A>
{
    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
    }
}

回答(2)

2 years ago

听起来你使用的是错误的界面 . IEqualityComparer<> 通常用于比较 other 类型实例的类 .

您的类型应该只执行 IEquatable<A> and override Equals(object)GetHashCode() . 注意签名 .

像这样:

public class A : B, IEquatable<A>
{
    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<A>
{
    public bool Equals(A x, A y)
    {
       //your implementation
    }

    public int GetHashCode(A x)
    {
        //your implementation
    }
}

有了这个其他选项,你需要 someEnumerableOfA.Distinct(new AEqualComparer ()) .

2 years ago

实现 IEqualityComparer<T> 不是 GetHashCodeEquals 的基本实现 .

实现 IEqualityComparer<T> 允许您将实现者的实例作为 T 的相等比较器提供 . 这是几个linq扩展和泛型集合构造函数的通用参数 .

覆盖 EqualsGetHashCode 会影响类的实例测试的相等性 . 利用调用 EqualsGetHashCode 的其他实现,如基本 =!= 运算符以及linq扩展和泛型集合构造函数,其中您不提供替代 IEqualityComparer<T> .

这些概念类似,但 serve different purposes, they are not partially interchangable .


让我展开一个例子,

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<A>
{
    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());

不同的ingnores A 的重载被覆盖 EqualsGetHashCode 并使用 AComparerInsensitive 来执行比较 .