IEquatable ,IEqualityComparer 之间的差异,以及在自定义对象集合上使用LINQ时覆盖.Equals()?
在比较自定义对象的两个集合时,我在使用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 newList = itemListA.Except(itemListB).ToList();
了解Linq如何进行比较,我发现了各种(冲突的?)方法,我们需要从IEquatable
或IEqualityComparer
等inheritance。
我很困惑,因为当我从IEquatable
inheritance时,我需要提供一个新的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
inheritance时,我可以使用IEquatable
或IEquatable
; 当我使用其中一个时, Equals()
签名的要求会发生变化。 推荐的方式是什么?
我想要完成的事情:
我希望能够使用Linq(Distinct / Except)以及标准的相等运算符( ==
和!=
)而无需复制代码。 如果两个对象的名称相同且 LastUpdate
属性在几秒(用户指定的)容差范围内,则应允许两个对象相等。
编辑:
显示GetHashCode()
代码。
是否覆盖object.GetHashCode
和object.GetHashCode
,实现IEquatable
或提供IEqualityComparer
IEquatable
。 所有这些都可以以稍微不同的方式工作。
1)从object
覆盖Equals
和GetHashCode
:
从某种意义上说,这是基本情况。 它通常会起作用,假设您能够编辑类型以确保两种方法的实现符合要求。 在许多情况下做这件事并没有错。
2)实现IEquatable
这里的关键点是你可以(并且应该)实现IEquatable
。 这个和#1之间的关键区别在于你有强大的Equals
方法类型,而不是让它使用object
。 这对程序员来说更方便(增加了类型安全性),也意味着任何值类型都不会被装箱,因此这可以提高自定义结构的性能。 如果你这样做,除了#1之外,你应该总是这样做,而不是代替。 这里的Equals
方法在function上与object.Equals
区别在于……糟糕。 不要那样做。
3)实现IEqualityComparer
这与前两个完全不同。 这里的想法是,对象没有得到它自己的哈希码,或者看它是否等于其他东西。 这种方法的关键在于对象不知道如何正确地获取它的哈希值,或者看它是否等于其他东西 。 也许是因为你没有控制类型的代码(即第三方库)而且他们没有打扰覆盖行为,或者他们确实覆盖了它,但你只想要自己独特的“平等”定义这个特定的背景。
在这种情况下,您将创建一个完全独立的“比较器”对象,该对象接收两个不同的对象并通知您它们是否相等,或者一个对象的哈希码是什么。 使用此解决方案时, Equals
或GetHashCode
方法在类型本身中执行的操作无关紧要 ,您将不会使用它。
请注意,所有这些都与==
运算符完全无关,运算符是它自己的野兽。
你不能“允许在LastUpdate
上有一些容忍”,然后使用一个使用LastUpdate
严格值的GetHashCode()
实现!
假设this
实例在23:13:13.933
具有LastUpdate
,并且obj
实例具有23:13:13.932
。 然后这两个可能与您的容忍想法相等。 但如果是这样,他们的哈希码必须是相同的数字。 但是,除非你非常幸运,否则这种情况不会发生,因为DateTime.GetHashCode()
不应该为这两次提供相同的哈希值。
此外,您的Equals
方法大多数是数学上的传递关系。 并且“近似等于”不能传递。 它的传递性封闭是标识一切的微不足道的关系。
我在对象中用于相等的基本模式如下。 请注意,只有2个方法具有特定于对象的实际逻辑。 其余的只是锅炉板代码,它们提供了这两种方法
class MyObject : IEquatable { 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
。 EqualityComparer
就足够了,因为它依赖于IEquatable
来执行相等性检查