IEqualityComparer 实现中 GetHashCode 和 Equals 之间的关系是啥? [复制]

Posted

技术标签:

【中文标题】IEqualityComparer 实现中 GetHashCode 和 Equals 之间的关系是啥? [复制]【英文标题】:What's the relation between GetHashCode and Equals in a IEqualityComparer implementation? [duplicate]IEqualityComparer 实现中 GetHashCode 和 Equals 之间的关系是什么? [复制] 【发布时间】:2014-04-08 07:48:14 【问题描述】:

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

只有当 A 的 GetHashCode 实现返回时,调试器才会到达 A 的 Equals 实现断点 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
    

【问题讨论】:

你能贴一些代码来解释这个问题吗,很难猜对 【参考方案1】:

听起来您使用了错误的界面。 IEqualityComparer&lt;&gt; 通常用于比较其他类型实例的类。

你的类型应该简单地实现IEquatable&lt;A&gt; 覆盖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 ())

【讨论】:

我选择 IEqualityComparer 的原因是因为我的解决方案依赖于调用 IEnumerable.Distinct(IEqualityComparer)。换句话说,如果我想返回我的集合的不同元素,为什么我不能在这里使用 Distinct 并因此需要提供 IEqualityComparer 实现? OP 实现了正确的接口,但方式错误。有关更多信息,请参阅链接的重复线程。 @Veverke,如果您覆盖 EqualsGetHashCode,则无需实现或提供 IEqualityComparer&lt;T&gt; @SriramSakthivel 不同意。你不应该有一个类A 实现IEqualityComparer&lt;A&gt;。那是滥用。看看String 类。它实现了IEquatable&lt;String&gt;。但是,还有其他类,例如实现 IEqualityComparer&lt;String&gt;StringComparer(但 IEqualityComparer&lt;StringComparer&gt;)。 @Jodrell:我对 Distinct 的 IEnumerable 调用将如何? variable.Distinct(this) - 当前类在哪里覆盖 Equals 和 GetHashCode?​​span> 【参考方案2】:

实现IEqualityComparer&lt;T&gt; 不是override 的基本实现GetHashCodeEquals

实现IEqualityComparer&lt;T&gt; 允许您提供实现者的实例作为T 的相等比较器。这是几个 linq 扩展和通用集合构造函数的通用参数。

覆盖EqualsGetHashCode 会影响对类实例进行相等性测试的方式。利用调用 EqualsGetHashCode 的其他实现,例如基本 =!= 运算符和 linq 扩展和通用集合构造函数,您不提供替代 IEqualityComparer&lt;T&gt;

这些概念相似,但用途不同,不能部分互换


让我用一个例子来扩展,

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

这种重载的不同忽略 As 被覆盖 EqualsGetHashCode 并使用 AComparerInsensitive 执行比较。

【讨论】:

@Veverke 我希望我的扩展答案能让事情更清楚。

以上是关于IEqualityComparer 实现中 GetHashCode 和 Equals 之间的关系是啥? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

如何实现 IEqualityComparer 以返回不同的值?

使用带有容差的 IEqualityComparer GetHashCode

在 IEqualityComparer 中包装委托

通用 IEqualityComparer 使用反射和属性来标记我想要比较的内容

List Except 操作,IEqualityComparer 使用

使用 IEqualityComparer 进行联合