LINQ用相等区分比较器默认值:IEquatable< T>实现忽略
本文关键字:IEquatable 实现 默认值 比较器 LINQ | 更新日期: 2023-09-27 18:15:20
我有一个类Foo
,其中Equals
和GetHashCode
方法已被覆盖的两个字段:
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
如果我注释掉Equals
和GetHashCode
覆盖,但保留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>
实现支持已经提供的Equals
和GetHashCode
覆盖,在最坏的情况下,它完全破坏了相等性比较。
我的问题:
- 为什么
IEquatable<T>
的独立实现会破坏相等性比较? - 是否可以独立于
Equals
和GetHashCode
覆盖而发挥作用? - 如果没有,为什么
EqualityComparer<T>.Default
首先寻找这个实现?
您的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>
的独立实现会破坏相等性比较?
因为你引入了一个不一致的实现。并不意味着在行为方面是独立的。它只是意味着通过避免类型检查(以及值类型的装箱)来提高效率。
- 它能独立于
Equals
和GetHashCode
覆盖发挥作用吗?
应该与Equals(object)
保持一致,并且必须与GetHashCode
保持一致,以保证正确性。
如果没有,为什么
EqualityComparer<T>.Default
首先寻找这个实现?
避免运行时类型检查和装箱/拆箱,主要是