什么时候==会以不同的方式覆盖.equals?

时间:2018-01-05 20:35:02

标签: c# equality

我理解==和.equals之间的区别。这里有很多其他问题可以解释细节上的差异,例如:这一个:What is the difference between .Equals and ==这个:Bitwise equality和其他许多人一样。

我的问题是:为什么他们两个(我意识到必须有一个很好的理由) - 他们似乎都做同样的事情(除非被覆盖不同)。

什么时候==会以不同的方式重载.equals被覆盖?

4 个答案:

答案 0 :(得分:37)

==在编译时是静态绑定的,因为运算符始终是静态的。你重载操作符 - 你不能覆盖它们。 Equals(object)以多态方式执行,因为它已被覆盖。

就你何时希望它们与众不同而言......

引用类型通常会覆盖Equals,但根本不会重载==。容易区分"这两个引用指的是同一个对象"和"这两个引用指的是相同的对象"。 (当然,如果有必要,您可以使用ReferenceEquals - 正如Eric在评论中指出的那样,这一点更加清晰。)您希望真正清楚何时这样做,请注意。

double对NaN值有此行为;当任一操作数为==(double, double)时,NaN将始终返回false,即使它们是相同的NaN。 Equals无法在不使合同无效的情况下执行此操作。 (Admittedly GetHashCode is broken for different NaN values, but that's a different matter ...)

我不记得曾经实施过它们,以便亲自给出不同的结果。

答案 1 :(得分:21)

  

我的问题是:为什么他们两个(我意识到必须有一个很好的理由)

如果有充分理由尚未向我解释。 C#中的平等比较是一个混乱的混乱,并且在我对C#的设计感到遗憾的事情列表中排名第9:

http://www.informit.com/articles/article.aspx?p=2425867

数学上,平等是最简单的等价关系,它应遵守规则:x == x应始终为真,x == y应始终与y == x相同,x == y如果x != yx == y为真,那么y == z应该始终具有相反的价值,那么x == z必须为真。 C#的==Equals机制保证了 none 这些属性! (但是,幸运的是,ReferenceEquals保证所有这些。)

正如Jon在回答中所说,基于两个操作数的编译时类型调度==,并根据.Equals(object)调度.Equals(T)IEquatable<T>左操作数的运行时类型。为什么这些调度机制的 是正确的?平等不是一个支持左手边的谓词,那么为什么有些但不是所有的实现都这样做呢?

我们想要的用户定义的相等性是一种多方法,其中两个操作数的运行时类型具有相同的权重,但这不是C#中存在的概念。

更糟糕的是,Equals==被赋予不同的语义是非常常见的 - 通常一个是引用相等而另一个是值相等。天真的开发者没有理由知道哪个是哪个,或者它们是不同的。这是一个很大的错误来源。当你意识到GetHashCode和Equals必须同意时,情况会变得更糟,但==不需要。

我是从头开始设计一种新语言,而我出于某些疯狂的原因希望运算符重载 - 我不这样做 - 那么我会设计一个更加直接的系统。类似于:如果您在某个类型上实施IComparable<T>,那么您会自动获得<<===!=等等,为您定义的运算符,它们的实施使它们保持一致。即x<=y必须具有x<y || x==y的语义以及!(x>y)的语义,并且x == y始终与y == x相同,依此类推。

现在,如果您的问题确实存在:

  

我们究竟如何陷入这种混乱的混乱局面?

然后我在2009年写下了一些想法:

https://blogs.msdn.microsoft.com/ericlippert/2009/04/09/double-your-dispatch-double-your-fun/

TLDR是:框架设计师和语言设计者有不同的目标和不同的约束,他们有时不会在设计中考虑这些因素,以确保整个平台的一致,合理的体验。这是设计过程的失败。

  

什么时候==会以不同的方式重载.equals被覆盖?

除非我有一个非常不寻常的,非常好的理由,否则我永远不会这样做。当我实现算术类型时,我总是将所有运算符实现为彼此一致。

答案 2 :(得分:0)

可能出现的一种情况是,您之前的代码库依赖于==的引用相等性,但您决定要添加值相等性检查。实现此目的的一种方法是实现IEquatable<T>,这很好,但现在假设只有引用的所有现有代码都相同?继承的Object.Equals应该与IEquatable<T>.Equals的工作方式不同吗?这并不容易回答,理想情况下,您希望所有这些功能/操作员以一致的方式行事。

对于发生这种情况的BCL中的具体案例,请查看TimeZoneInfo。在这种特殊情况下,==Object.Equals保持不变,但并不清楚这是最佳选择。

另外,一种可以缓解上述问题的方法是使类不可变。在这种情况下,代码不太可能因先前依赖引用相等性而被破坏,因为您不能通过引用来改变实例并使之前检查过的相等无效。

答案 3 :(得分:0)

通常,您希望他们做同样的事情,特别是如果您的代码将由除您自己以外的任何人和您旁边的人使用。理想情况下,对于使用您的代码的任何人,您都希望遵循最小意外原则,这种原则会随机违反行为。说完这个:

重载相等通常是一个坏主意,除非类型是不可变的,并且是密封的。如果你正处于你不得不提出问题的阶段,那么在任何其他情况下纠正它的可能性都很小。这有很多原因:

一个。 Equals和GetHashCode一起使用以使字典和散列集工作 - 如果您的实现不一致(或者散列代码随时间变化),则可能出现以下情况之一:

  • 字典/集合开始执行有效的线性时间查找。
  • 项目在字典/集中丢失

B中。你真的想做什么?通常,面向对象语言中的对象的身份是它的参考。因此,拥有两个具有不同引用的相同对象只会浪费内存。首先可能没有必要创建副本。

℃。当您开始实现对象的相等性时,您经常发现的是您正在寻找“用于特定目的”的相等定义。这使得为​​此目的烧掉你的唯一Equals是一个非常糟糕的主意 - 更好地为使用定义不同的EqualityComparers。

d。正如其他人指出的那样,你重载运算符但重写方法。这意味着除非操作符调用方法,否则当有人试图使用==并且发现错误的(意外的)方法在层次结构的错误级别被调用时,会发生非常有趣和不一致的结果。