25

在比较自定义对象的两个集合时,我在使用 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>orIEqualityComparer<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>or IEquatable<object>; Equals()当我使用其中一种时,签名的要求会发生变化。推荐的方法是什么?

我要完成的工作:

我希望能够在不重复代码的情况下使用 Linq (Distinct/Except) 以及标准相等运算符 ( ==and )。如果两个对象的名称相同并且属性在几秒(用户指定的)容差范围内,则!=比较应该允许将两个对象视为相等。LastUpdate

编辑:

显示GetHashCode()代码。

4

3 回答 3

28

无论您是否覆盖object.Equalsand object.GetHashCode、实现IEquatable或提供IEqualityComparer. 它们都可以工作,只是方式略有不同。

1)覆盖EqualsGetHashCode来自object

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

2) 实施IEquatable

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

3) 实施IEqualityComparer

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

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


请注意,所有这些都与操作员完全无关,==操作员是自己的野兽。

于 2013-04-03T21:13:27.427 回答
6

我在对象中用于相等的基本模式如下。请注意,只有 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>执行相等性检查

于 2013-04-03T20:53:46.273 回答
5

您不能“允许一些公差LastUpdate”,然后使用GetHashCode()使用严格值的实现LastUpdate

假设this实例有LastUpdateat 23:13:13.933,并且obj实例有23:13:13.932。那么这两个可能与您的宽容想法相当。但如果是这样,它们的哈希码必须是相同的数字。但这不会发生,除非你非常幸运,因为DateTime.GetHashCode()这两次不应该给出相同的哈希值。

此外,您的Equals方法在数学上大多数是传递关系。并且“大约等于”不能传递。它的传递闭包是标识一切的微不足道的关系。

于 2013-04-03T21:12:34.813 回答