关于c#:IEqualityComparer实现中GetHashCode和Equals之间的关系是什么?

What's the relation between GetHashCode and Equals in a IEqualityComparer implementation?

本问题已经有最佳答案,请猛点这里访问。

我有一个从B类继承并实现IEqualityComparer的类A。这意味着类A提供了它自己的equals和gethashcode方法的实现。到现在为止,一直都还不错。问题是,我不明白代码的行为方式为何如下:

如果a的getHashCode实现返回,调试器将只到达a的equals实现断点this.GetHashCode()而不是obj.GetHashCode(),其中"obj"是gethashcode签名定义的参数(在我的例子中是类型A的变量)。

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

为什么会这样?

代码演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
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
    }
}


听起来您使用的界面不正确。IEqualityComparer<>通常用于比较其他类型实例的类。

您的类型应该简单地实现IEquatable,并重写Equals(object)GetHashCode()。注意签名。

这样地:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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方法将使用你的实现。

另一种选择是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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并不是overrideEquals的基础实施。

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

覆盖EqualsGetHashCode的效果是测试类实例是否相等的方式。利用调用EqualsGetHashCode的其他实现,如基础=!=运算符、LINQ扩展和一般集合构造函数,其中不提供替代IEqualityComparer

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

让我用一个例子来展开,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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扩展之后

1
var distinct = aSequneceOfA.Distinct();

distinct不包含同时具有相同Value2和顺序可比Value1的任何实例。实现这一点不需要其他接口实现。

现在,假设在某些情况下,我对Value1的顺序比较不满意,也许我需要一些案例不敏感。我可以实现一个新的平等比较器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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的替代过载,

1
2
var insensitivelyDistinct = aSequneceOfA.Distinct(
    new AComparerInsensitive());

不同的Ingnores A的超负荷覆盖了EqualsGetHashCode,并使用AComparerInsensitive进行比较。