IEquatable <T>,IEqualityComparer <T>之间的差异,以及在自定义对象集合上使用LINQ时重写.Equals()?

JYe*_*ton 25 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
Run Code Online (Sandbox Code Playgroud)

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

List<myObject> newList = itemListA.Except(itemListB).ToList();
Run Code Online (Sandbox Code Playgroud)

了解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
}
Run Code Online (Sandbox Code Playgroud)

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

我想要完成的事情:

我希望能够使用Linq(Distinct/Except)以及标准的相等运算符(==!=)而无需复制代码.比较应该允许要考虑两个对象相等,如果他们的名字是相同的并且所述LastUpdate属性是若干秒(用户指定的)的公差范围内.

编辑:

显示GetHashCode()代码.

Ser*_*rvy 28

不要紧,你是否会抵消object.Equalsobject.GetHashCode,执行IEquatable,或提供IEqualityComparer.所有这些都可以以稍微不同的方式工作.

1)覆盖EqualsGetHashCode来自object:

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

2)实施 IEquatable

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

3)实施 IEqualityComparer

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

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


请注意,所有这些都与==运营商完全无关,运营商是自己的野兽.

  • 除#1之外,仅做#2非常重要.实现`IEquatable <>`而不覆盖`GetHashCode()`并没有给出编译器警告(遗憾的是),但它会导致一种类型在`Dictionary <YourTypeHere,X>`,`HashSet <YourTypeHere>`中出现故障,并且很多其他情况(比如上面原始问题中的`Except`方法,或者`Distinct`方法),其中使用了'EqualityComparer <YourTypeHere> .Default`比较器(当然是显式的). (3认同)

Jar*_*Par 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);
  }
}
Run Code Online (Sandbox Code Playgroud)

如果您遵循此模式,则实际上不需要提供自定义IEqualityComparer<MyObject>.这EqualityComparer<MyObject>.Default将是足够的,因为它将依赖于IEquatable<MyObject>执行相等性检查


Jep*_*sen 5

你不能"允许一些容忍LastUpdate",然后使用一个GetHashCode()使用严格值的实现LastUpdate!

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

此外,您的Equals方法在数学上最多是传递关系.并且"近似等于"不能传递.它的传递性封闭是识别一切的微不足道的关系.