我理解==和.equals之间的区别。这里有很多其他问题可以解释细节上的差异,例如:这一个:What is the difference between .Equals and ==这个:Bitwise equality和其他许多人一样。
我的问题是:为什么他们两个(我意识到必须有一个很好的理由) - 他们似乎都做同样的事情(除非被覆盖不同)。
什么时候==会以不同的方式重载.equals被覆盖?
答案 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 != y
和x == 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。正如其他人指出的那样,你重载运算符但重写方法。这意味着除非操作符调用方法,否则当有人试图使用==并且发现错误的(意外的)方法在层次结构的错误级别被调用时,会发生非常有趣和不一致的结果。