LINQ用相等区分比较器默认值:IEquatable< T>实现忽略

本文关键字:IEquatable 实现 默认值 比较器 LINQ | 更新日期: 2023-09-27 18:15:20

我有一个类Foo,其中EqualsGetHashCode方法已被覆盖的两个字段:

public class Foo
{
    private readonly int _x;
    private readonly int _y;
    public Foo(int x, int y) { _x = x; _y = y; }
    public override bool Equals(object obj) {
        Foo other = obj as Foo;
        return other != null && _y == other._y;
    }
    public override int GetHashCode() { return _y; }
}

如果我创建一个Foo:s数组并计算该数组的Distinct值的数量:

var array = new[] { new Foo(1, 1), new Foo(1, 2), new Foo(2, 2), new Foo(3, 2) };
Console.WriteLine(array.Distinct().Count());

不同值的数目被识别为:

2

如果我现在让我的类Foo实现IEquatable<Foo>使用以下实现:

public bool Equals(Foo other) { return _y == other._y; }

不同值的个数仍然是:

2

但是如果我把实现改成这样:

public bool Equals(Foo other) { return _x == other._x; }

计算出的不同Foo:s的个数既不是3(即不同_x的个数)也不是2(不同_y的个数),而是:

4

如果我注释掉EqualsGetHashCode覆盖,但保留IEquatable<Foo>实现,答案也是4

根据MSDN文档,这个Distinct重载应该使用静态属性EqualityComparer。默认定义相等比较,并且:

The Default property checks whether type T implements the System.IEquatable<T>
interface and, if so, returns an EqualityComparer<T> that uses that 
implementation. Otherwise, it returns an EqualityComparer<T> that uses the 
overrides of Object.Equals and Object.GetHashCode provided by T.

但是看看上面的实验,这个说法似乎不成立。在最好的情况下,IEquatable<Foo>实现支持已经提供的EqualsGetHashCode覆盖,在最坏的情况下,它完全破坏了相等性比较。

我的问题:

  • 为什么IEquatable<T>的独立实现会破坏相等性比较?
  • 是否可以独立于EqualsGetHashCode覆盖而发挥作用?
  • 如果没有,为什么EqualityComparer<T>.Default首先寻找这个实现?

LINQ用相等区分比较器<T>默认值:IEquatable< T>实现忽略

您的GetHashCode方法取决于y。这意味着如果你的Equals方法不依赖于y,你就破坏了平等契约…他们是不一致的。

Distinct()将期望相等的元素具有相同的哈希码。在您的示例中,x值的唯一相等元素具有不同的哈希码,因此Equals甚至不会被调用。

来自IEquatable<T>.Equals的文档:

如果你实现了Equals,你也应该覆盖Object.Equals(Object)GetHashCode的基类实现,以便它们的行为与IEquatable<T>.Equals方法的行为一致。

您的Equals(Foo)的实现与 Equals(object) GetHashCode不一致。

EqualityComparer<T>.Default仍然会委托给你的GetHashCode方法——它只会优先使用你的Equals(T)方法而不是你的Equals(object)方法。

那么按顺序回答你的问题:

  • 为什么IEquatable<T>的独立实现会破坏相等性比较?

因为你引入了一个不一致的实现。并不意味着在行为方面是独立的。它只是意味着通过避免类型检查(以及值类型的装箱)来提高效率。

  • 它能独立于EqualsGetHashCode覆盖发挥作用吗?

应该与Equals(object)保持一致,并且必须与GetHashCode保持一致,以保证正确性。

如果没有,为什么EqualityComparer<T>.Default首先寻找这个实现?

避免运行时类型检查和装箱/拆箱,主要是