LINQ与EqualityComparer不同<t> .Default:忽略IEquatable <t>实现?</t> </t>

时间:2013-05-26 19:36:43

标签: 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; }
}

如果我创建一个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 documentation,此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.

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

我的问题:

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

1 个答案:

答案 0 :(得分:4)

GetHashCode方法取决于y。这意味着如果您的Equals方法依赖于y,那么您就违反了平等合同......它们是不一致的。

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

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

  

如果实现Equals,则还应覆盖Object.Equals(Object)GetHashCode的基类实现,以使其行为与IEquatable<T>.Equals方法的行为一致。 / p>

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

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

所以按顺序回答你的问题:

  
      
  • 为什么独立实施IEquatable<T>会破坏平等比较?
  •   

因为您引入了一个不一致的实现。 意味着意味着在行为方面是独立的。这只是为了通过避免类型检查(和拳击,值类型)来提高效率。

  
      
  • 它是否可以独立于EqualsGetHashCode覆盖而发挥作用?
  •   

为了理智,它应该与Equals(object)一致,为了正确起见,必须GetHashCode保持一致。

  

如果没有,为什么EqualityComparer<T>.Default首先要查找此实现?

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