在阅读Jeffrey Richter的CLR via C# 4th edition(微软出版社)时,作者曾指出虽然Object.Equals
目前检查身份相同,但should have implemented the method like this:
public class Object {
public virtual Boolean Equals(Object obj) {
// The given object to compare to can't be null
if (obj == null) return false;
// If objects are different types, they can't be equal.
if (this.GetType() != obj.GetType()) return false;
// If objects are same type, return true if all of their fields match
// Because System.Object defines no fields, the fields match
return true;
}
}
这让我很奇怪:默认情况下,相同类型的每个非null对象都是?因此,除非被覆盖:类型的所有实例都相等(例如,所有锁定对象都相等),并返回相同的哈希码。假设==
Object
仍然检查引用相等,这意味着(a == b) != a.Equals(b)
也会很奇怪。
我认为事物是平等的,如果它是完全相同的(身份)是一个更好的想法,而不仅仅是让一切都平等,除非被覆盖。但这是微软出版的一本着名的第四版,所以这个想法必定有一些优点。我阅读了文本的其余部分,但不禁怀疑:为什么作者会这样做呢?我在这里错过了什么? Richter实现相对于当前Object.Equals实现的巨大优势是什么?
答案 0 :(得分:5)
当前默认值Equals()
执行所谓的浅层比较(或参考比较),然后如果引用不同则不再检查。
对于基础实现,我认为这是完全可以接受的。我当然不会认为这是错误的或不完整的。
你引用的Richter的例子 1 对于基础System.Object 来说也是完全合法的。他的实现的问题在于它可以说应该被声明为abstract 2 - 如果你没有覆盖它,你将在派生对象上最终得到一个不可靠的Equals()
(因为{{ 1}}应该进行深度比较)。必须在所有派生对象上覆盖此方法将会有很多工作,因此Microsoft方法更好作为默认。所以在本质上你是正确的:里希特的例子很奇怪 - 最好默认为不相等而不是反过来(默认为Equals()
会导致一些相当有趣的行为,如果人们忘记覆盖它)。 / p>
(仅供参考,这是本书中公布的默认实现)
1:里希特是一个聪明的人,他知道他的东西,我一般不会与他说的任何事情争论。你必须明白,MS工程师必须长时间地思考很多事情,因为他们知道他们没有灵活性能够弄错,然后才能解决问题。无论他们多么正确,人们总会在以后再次猜测它们,并提供其他意见。这并不意味着原始错误或替代方案是错误的 - 它只是意味着有另一种选择。
2:当然这意味着没有基本实现,这很好,因为它不可靠。
答案 1 :(得分:3)
Jeffery Richter谈论 Value 与身份平等的平等。
具体你问:
因此,除非被覆盖:类型的所有实例都相等?
答案是是, 但是...... 如同,是,但是它是(几乎)总是应该被覆盖。
因此,对于大多数类,应该重写它以进行逐个属性比较以确定相等性。对于其他一些真正基于身份的类(如锁),应该重写它以使用与现在相同的技术。
关键是,几乎在每种情况下都必须覆盖它,而这一点就足够困难,笨拙且容易出错,这可能是微软不使用这种方法的原因。
价值平等优于身份平等的优势是什么?这是因为如果两个不同的对象具有相同的值/内容,那么在诸如Dictionary对象的Keys之类的情况下,它们可以被认为是“相等的”。
或者考虑.Net中的字符串问题,它们实际上是对象,但在较高级别(特别是在VB.net中)被视为很多值。当你想要比较两个字符串是否相等时会出现问题,因为99%的时候你真的不在乎它们是不同的对象实例,你只关心它们是否包含相同的文本。所以.Net必须确保字符串比较实际上是这样的,即使它们确实是对象。
答案 2 :(得分:1)
如果要求一个人列出任意类型的所有可识别的不同对象,并且没有给出任何对象是什么或将用于什么的指示,那么唯一普遍适用的测试方法是引用应被视为指向可识别的不同对象Object.Equals(Object)
。如果更改当前指向X
的一个或多个引用,以便它们反而指向Y
可能会改变程序行为,则应将两个引用X
和Y
视为可识别 - 不同
例如,如果string
的两个实例都包含战争与和平的整个文本,标点符号和格式相同,则可能会替换第一个引用的一些或所有引用到第二个,反之亦然,对程序执行影响很小或没有影响,超出了指向同一实例的两个引用之间的比较可以比两个指向不同字符串的引用更快地保存相同文本的事实包含相同字符的内容。
在大多数情况下,如果保存不可变数据的对象相同,则应认为它们是相同的。存在用于保存可变数据或存在用作身份令牌的对象通常应被视为彼此不同。鉴于可以定义一个自定义EqualityComparer
,它将被视为不完全等价的等效对象(例如,不区分大小写的字符串比较器),并且假定需要某些等价定义的代码比严格等价更宽通常知道它正在使用什么类型以及等价的定义是合适的,通常最好将Object.Equals
报告对象视为不同,除非它们被设计为可替换的(例如,字符串)。 / p>
为了使用真实世界的类比,假设给出一张纸,每张纸上写有车辆识别号,并询问第一张纸识别的车是否与识别的车相同到了第二个。如果两张纸的VIN具有相同的VIN,则第一张识别的汽车显然与第二张识别的汽车相同。然而,如果它们具有不同的VIN,排除具有多于一个VIN的汽车的任何奇怪可能性,则它们识别不同的汽车。即使汽车有相同的品牌和型号,选项包,油漆方案等,他们仍然会是不同的汽车。购买一个人的人无权随意开始使用另一个人。有时候知道两辆车目前是否有相同的选项包等可能是有用的,但如果这是人们想知道的,那就是人们应该问的问题。
答案 3 :(得分:0)
猜猜:Object.Equals
的当前行为并不是大多数人认为“平等”的行为。
此方法存在的主要(唯一?)原因是允许通过伪装成“==”实现来搜索集合中的项目。因此,在大多数实际情况下,此实现会出现意外行为(除非您想要查找特定实例是否已在集合中),并强制为您提供自定义比较函数...
可能它是Object的方法,因为技术原因。即对于Array / Dictionary,假设所有对象都有Equal
/ GetHash
而不是检查对象上的某些内容以启用“查找”功能可能会更快。
可以说它根本不应该在Object上,而只需要可以存储在集合中的类来实现某种形式的IComparable
接口。