在自定义对象集合上使用LINQ时IEquatable <t>,IEqualityComparer <t>和重写.Equals()之间的区别?</t> </t>

时间:2013-04-03 20:46:12

标签: c# linq

在比较自定义对象的两个集合时,使用Linq的.Except()方法时遇到了一些困难。

我从Object派生了我的课程,并为Equals()GetHashCode()以及运算符==!=实施了覆盖。我还创建了一个CompareTo()方法。

在我的两个馆藏中,作为调试实验,我从每个列表中取出了第一个项目(这是重复的)并将它们进行比较如下:

itemListA[0].Equals(itemListB[0]);     // true
itemListA[0] == itemListB[0];          // true
itemListA[0].CompareTo(itemListB[0]);  // 0

在所有三种情况下,结果都是我想要的。但是,当我使用Linq的Except()方法时,重复的项目被删除:

List<myObject> newList = itemListA.Except(itemListB).ToList();

了解Linq如何进行比较,我发现了各种(冲突的?)方法,说我需要继承IEquatable<T>IEqualityComparer<T>等。

我很困惑,因为当我从IEquatable<T>继承时,我需要提供一个新的Equals()方法,其签名与我已经覆盖的签名不同。我是否需要使用两种不同签名的方法,或者我是否不再从Object派生我的课程?

我的对象定义(简化)如下所示:

public class MyObject : Object
{
    public string Name {get; set;}
    public DateTime LastUpdate {get; set;}

    public int CompareTo(MyObject other)
    {
        // ...
    }

    public override bool Equals(object obj)
    {
        // allows some tolerance on LastUpdate
    }

    public override int GetHashCode()
    {
        unchecked
        {
            int hash = 17;
            hash = hash * 23 + Name.GetHashCode();
            hash = hash * 23 + LastUpdate.GetHashCode();
            return hash;
        }
    }

    // Overrides for operators
}

我注意到,当我从IEquatable<T>继承时,我可以使用IEquatable<MyObject>IEquatable<object>这样做;当我使用其中一个时,Equals()签名的要求会发生变化。推荐的方式是什么?

我想要完成的任务:

我希望能够使用Linq(Distinct / Except)以及标准的相等运算符(==!=),而无需复制代码。如果两个对象的名称相同 LastUpdate属性在几秒(用户指定的)容差范围内,则应该允许两个对象相等。

修改

显示GetHashCode()代码。

3 个答案:

答案 0 :(得分:28)

覆盖object.Equalsobject.GetHashCode,实施IEquatable或提供IEqualityComparer并不重要。所有这些都可以工作,只是略有不同。

1)从Equals覆盖GetHashCodeobject

从某种意义上说,这是基本情况。它通常会起作用,假设您能够编辑类型以确保两种方法的实现符合要求。在许多情况下做这件事并没有错。

2)实施IEquatable

这里的关键点是你可以(而且应该)实施IEquatable<YourTypeHere>。这与#1之间的主要区别在于,您为Equals方法打字时强大,而不是仅使用object。这对程序员来说更方便(增加了类型安全性),也意味着任何值类型都不会被装箱,因此这可以提高自定义结构的性能。如果你这样做,除了#1 之外,你应该总是这样做,而不是。这里Equals方法的功能与object.Equals不同,那将是......不好的。不要那样做。

3)实施IEqualityComparer

这与前两个完全不同。这里的想法是对象没有得到它自己的哈希码,或者看它是否等于其他东西。这种方法的重点是一个对象不知道如何正确地获取它的哈希值,或者看它是否等于其他东西。也许是因为你没有控制类型的代码(即第三方库),他们也没有费心去覆盖这个行为,或者他们确实覆盖了它,但你只想要自己独特的“平等”定义。这个特定的背景。

在这种情况下,您将创建一个完全独立的“comparer”对象,该对象接收两个不同的对象并通知您它们是否相等,或者一个对象的哈希码是什么。使用此解决方案时,EqualsGetHashCode方法在类型本身中的作用并不重要,您将不会使用它。


请注意,所有这些都与==运算符完全无关,运算符是它自己的野兽。

答案 1 :(得分:5)

我在对象中用于相等的基本模式如下。请注意,只有2个方法具有特定于对象的实际逻辑。其余的只是用于这两种方法的锅炉板代码

class MyObject : IEquatable<MyObject> { 
  public bool Equals(MyObject other) { 
    if (Object.ReferenceEquals(other, null)) {
      return false;
    }

    // Actual equality logic here
  }

  public override int GetHashCode() { 
    // Actual Hashcode logic here
  }

  public override bool Equals(Object obj) {
    return Equals(obj as MyObject);
  }

  public static bool operator==(MyObject left, MyObject right) { 
    if (Object.ReferenceEquals(left, null)) {
      return Object.ReferenceEquals(right, null);
    }
    return left.Equals(right);
  }

  public static bool operator!=(MyObject left, MyObject right) {
    return !(left == right);
  }
}

如果您遵循此模式,则无需提供自定义IEqualityComparer<MyObject>EqualityComparer<MyObject>.Default就足够了,因为它依赖于IEquatable<MyObject>来执行相等性检查

答案 2 :(得分:5)

您不能“允许LastUpdate上的某些容忍”,然后使用GetHashCode()使用LastUpdate的严格值的实施!

假设this实例在LastUpdate23:13:13.933obj实例有23:13:13.932。然后这两个可能与您的容忍想法相等。但如果是这样,他们的哈希码 必须 是相同的数字。但除非你非常幸运,否则这种情况不会发生,因为DateTime.GetHashCode()不应该为这两次提供相同的哈希值。

此外,您的Equals方法在数学上最多是传递关系。并且“近似等于”不能传递。它的传递性闭包是识别一切的简单关系。