LINQ Distinct 与 EqualityComparer<T>.Default:IEquatable<T> 实现被忽略?

And*_*son 2 c# linq iequatable

我有一个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; }
}
Run Code Online (Sandbox Code Playgroud)

如果我创建一个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());
Run Code Online (Sandbox Code Playgroud)

不同值的数量被识别为:

2
Run Code Online (Sandbox Code Playgroud)

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

public bool Equals(Foo other) { return _y == other._y; }
Run Code Online (Sandbox Code Playgroud)

不同值的数量仍然是:

2
Run Code Online (Sandbox Code Playgroud)

但是,如果我将实现更改为:

public bool Equals(Foo other) { return _x == other._x; }
Run Code Online (Sandbox Code Playgroud)

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

4
Run Code Online (Sandbox Code Playgroud)

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

根据MSDN 文档,此Distinct重载应使用静态属性EqualityComparer.Default来定义相等比较,并且:

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.
Run Code Online (Sandbox Code Playgroud)

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

我的问题:

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

Jon*_*eet 6

您的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要先寻找这个实现?

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