【问题标题】:Is it important to override Equals if I'm implementing IEquatable<T>?如果我正在实现 IEquatable<T>,重写 Equals 是否重要?
【发布时间】:2012-11-24 08:54:27
【问题描述】:

我知道在实现自定义相等检查时覆盖GetHashCode 的重要性——为此我实现了IEquality&lt;T&gt; 接口,以及泛型和非泛型Equalsas discussed here. 之间的区别现在有一点覆盖Equals(object t)?不是所有东西都属于通用Equals(T t)吗?

public override int GetHashCode() //required for hashsets and dictionaries
{
    return Id;
}

public bool Equals(T other) //IEquatable<T> here
{
    return Id == other.Id;
}

public override bool Equals(object obj) //required??
{
    return Equals(obj as T);
}

【问题讨论】:

  • 注意那里的实现。在通用版本中,您应该将other 考虑为null,因为如果other 不是Tobj as T 将导致 null。
  • @Jamiec 是的,这只是一个例子。我意识到。谢谢
  • 太好了,很高兴您知道。更多地考虑未来的访问者,他们可能会看到这个问题并认为这是实施IEquatable&lt;T&gt; 的正确方法,但它缺少一个重要部分。

标签: c# generics equality iequatable


【解决方案1】:

未密封的类型不应实现IEquatable&lt;T&gt;,因为确保(甚至可能)派生类型正确实现它的唯一方法是实现IEquatable&lt;T&gt;.Equals(T),以便调用Object.Equals(Object)。由于IEquatable&lt;T&gt; 的全部目的是避免在比较它们之前浪费CPU 时间将它们转换为Object,因此调用Object.Equals(Object) 的实现不能比不使用接口时实现的默认行为更快。实施。

通过实现IEquatable&lt;T&gt;,密封类类型可能会获得轻微的性能优势;首选样式是让Object.Equals(Object) 尝试将参数转换为T;如果转换成功,使用IEquatable&lt;T&gt;.Equals 实现;否则返回false。在任何情况下,IEquatable.Equals(T)Object.Equals(Object) 在传递相同的对象实例时都不应产生不同的结果。

结构类型可以使用与密封类类型相同的模式。尝试转换的方法有点不同,因为失败的转换不能返回null,但模式应该还是一样的。如果一个结构实现了一个变异接口(例如List&lt;T&gt;.Enumerator,一个实现IEnumerator&lt;T&gt;的结构),相等比较的正确行为有点模糊。

注意,顺便说一句,IComparable&lt;T&gt;IEquatable&lt;T&gt; 应该被认为是相互独立的。当X.CompareTo(Y) 为零时,X.Equals(Y) 将返回 true,但某些类型可能具有自然排序,其中两个事物可能不同,而没有一个关于另一个事物的排名。例如,可能有一个NamedThing&lt;T&gt; 类型,它结合了stringT。这种类型将支持名称的自然排序,但不一定支持T。名称匹配但 T 不同的两个实例应返回 0 对应 CompareTo,但返回 false 对应 Equals。因此,如果 Equals 未更改,则覆盖 IComparable 不需要覆盖 GetHashCode

【讨论】:

  • 总体上出色的洞察力,但不完全是在问题上,+1-ed。我对您对 iequatable 仅对密封类型有效的想法有一点怀疑。让我说我有Person : IEquatable&lt;Person&gt; unsealed,它在Equals 上实现,它只是return Id == other.Id,考虑到IdPerson 类上的一个字段。现在,如果我将Person 派生为Student,并且Student 没有覆盖任何内容,那么您对性能损失以及再次使用非通用Equals 有何意义?你能用上面的例子更详细地解释一下吗?
  • 我在想,如果必须比较 Student 是否相等,难道不会再次使用 Person 类上的相同 Equals 方法吗?或者您是说将使用Student 的默认非泛型引用相等检查Equals
  • @nawfal:如果Student 没有实现IEquatable&lt;Student&gt;,那么Student 的散列集合将使用虚拟Object.Equals(Object) 方法。如果它确实实现了IEquatable&lt;Student&gt;,那么它必须要么有这样的实现链到基类IEquatable&lt;Person&gt;.Equals 方法,要么硬编码它自己的那个方法的副本。前者会否定实现IEquatable&lt;Student&gt; 的任何性能优势,如果IEquatable&lt;Person&gt; 的行为发生变化,后者可能会破坏事情。在...上使用IEquatable&lt;T&gt; 的性能优势...
  • ...结构类型通常很大。类类型可实现的最大性能优势要小得多。如果不需要牺牲任何东西,那么即使是在大量使用的代码中的小幅性能提升也值得一试,但限制派生类的行为似乎并不是一个胜利。此外,我认为更简洁的设计可能是拥有一个包含 ID 和可继承 PersonInfo 对象的密封类型,其中后一个对象的比较方法将进行完整比较,而前一个对象将指定一个不变量。 ..
  • ...具有相同 ID 的两个实例必须始终持有对彼此比较相等的 PersonInfo 实例的引用。这将阐明 ID 和对象内容之间的关系,即使此类派生添加更多字段,PersonInfo 的派生也将继续适用。
【解决方案2】:

您当然应该覆盖Equals(object t) - 否则在使用该重载时您可能会得到不正确的结果。您不能假设 Equals(T other) 是将被调用的重载。

如果您不覆盖它,将使用引用相等,这意味着类似以下内容将返回 false:

myObject1.Id = 1;
myObject2.Id = 1;

myObject1.Equals((object)myObject2); // false!

另一个可能的问题是继承类 - 如果你将你的类型与 inheriting 类型进行比较,这很容易失败。

【讨论】:

  • 我在问什么时候可以调用非泛型 Equals?那么泛型 Equals 的意义何在?对不起,我觉得这个答案不完整
  • @nawfal - 在我的回答中添加了详细信息。
  • 好的,我现在明白了。但是显示的示例非常罕见,我仍然明白这一点。
  • @nawfal - 给出的例子是为了说明可能的问题。
  • @nawfal Another possible problem is with inheriting classes - if you compare your type with an inheriting type, this can easily fail. 为什么会这样?如果您比较继承 T 的类型,我会认为 Equal(T) 会被调用。
【解决方案3】:

来自msdn

如果你实现了 IEquatable,你还应该重写基 Object.Equals(Object) 和 GetHashCode 的类实现,以便 它们的行为与 IEquatable.Equals 的行为一致 方法。如果你确实覆盖了 Object.Equals(Object),你的覆盖 在对静态的调用中也会调用实现 类上的 Equals(System.Object, System.Object) 方法。在 此外,您应该重载 op_Equality 和 op_Inequality 运营商。这确保了所有相等性测试返回一致 结果。

我也可以做一个更好的谷歌搜索。这是关于这个主题的good article on msdn blogs by JaredPar

简而言之,覆盖Equals(object obj)

object 类上的静态 Equals(object obj1, object obj2) 方法或 Contains(object item) 需要em>ArrayList 实例等。

接受这一点,因为该链接在该主题上更加全面。也感谢 Oded..

【讨论】:

    猜你喜欢
    • 2016-04-24
    • 2011-09-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多