7

在通过 C# 第 4 版(Microsoft Press)阅读 Jeffrey Richter 的CLR时,作者曾指出,在Object.Equals当前检查身份相等性时,Microsoft应该已经实现了这样的方法

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;
    }
}

这让我觉得很奇怪:默认情况下,相同类型的每个非空对象都是相等的?因此,除非被覆盖:一个类型的所有实例都是相等的(例如,所有锁定对象都相等),并返回相同的哈希码。并假设==onObject仍然检查引用相等,这意味着(a == b) != a.Equals(b)这也很奇怪。

我认为,如果事物完全相同(身份),则事物平等的想法比仅使一切都平等(除非被覆盖)更好的想法。但这是微软出版的著名书籍的第 4 版,所以这个想法一定有一些可取之处。我阅读了其余的文字,但不禁想知道:作者为什么会提出这个建议?我在这里想念什么?与当前的 Object.Equals 实现相比,Richter 的实现的最大优势是什么?

4

4 回答 4

5

当前默认值Equals()执行所谓的浅比较(或引用比较),如果引用不同,则不会进一步检查。

我认为这对于基本实现是完全可以接受的。我当然不会认为它是错误的或不完整的。

您引用的Richter 示例1对于基本 System.Object也是完全合法的。他的实现的问题在于它可以说应该被声明为抽象2Equals() -如果你不覆盖它,你将使用他的方法最终得到一个不可靠的派生对象(因为Equals()应该进行深度比较)。必须在所有派生对象上重写此方法将需要大量工作,因此 Microsoft 方法作为默认的. 所以从本质上讲你是对的:Richter 的例子很奇怪——最好默认不等于而不是反过来(true如果人们忘记覆盖它,默认会导致一些相当有趣的行为)。

(只是为了方便参考,这里是书中发布的默认实现)

在此处输入图像描述



1:里希特是个聪明人,他知道自己的事,我一般不会反对他说的任何话。您必须了解,MS 工程师必须对很多事情进行长时间而艰苦的思考,因为他们知道他们没有能够将其弄错然后稍后再修复的灵活性。无论他们多么正确,人们总是会在以后再次猜测他们,并提供不同的意见。这并不意味着原件是错误的或替代品是错误的——它只是意味着有替代品。

2:这当然意味着没有基础实现,这很好,因为它本来就不可靠。

于 2013-02-06T02:35:03.567 回答
3

Jeffery Richter 正在谈论价值平等而不是身份平等。

你具体问:

所以除非被覆盖:一个类型的所有实例都是平等的?

答案是Yes , But... 如, Yes, But它(几乎)总是应该被覆盖。

因此,对于大多数类,应该重写它来进行逐个属性的比较以确定相等性。对于其他一些真正基于身份的类(如锁),应该重写它以使用与今天相同的技术。

但关键是它必须在几乎所有情况下都被覆盖,仅此一项就足够困难、笨拙和容易出错,这可能是微软没有使用这种方法的原因。


价值平等相对于身份平等的优势是什么?如果两个不同的对象具有相同的值/内容,那么它们可以被认为是“相等的”,以便在字典对象的键等情况下进行比较。

或者考虑 .Net 中的字符串问题,它们实际上是对象,但在更高级别(尤其是在 VB.net 中)被视为非常相似的值。当您想要比较两个字符串是否相等时,这会出现一个问题,因为 99% 的时间您真的不关心它们是否是不同的对象实例,您只关心它们是否包含相同的文本。因此,.Net 必须确保字符串比较实际上是这样工作的,即使它们确实是对象。

于 2013-02-06T02:36:06.573 回答
1

如果一个人被要求列出所有可识别的任意类型的对象,并且没有给出任何关于对象是什么或它们将用于什么的指示,那么测试两个引用是否应该是唯一普遍适用的方法被认为指向可识别的不同对象的是Object.Equals(Object)。如果更改当前指向的一个或多个引用以使它们改为指向可能会改变程序行为X,则两个引用Y应该被认为是可识别的。XY

例如,如果两者的两个实例string都包含War and Peace的完整文本,标点符号和格式相同,则可能会将对第一个的部分或全部引用替换为对第二个的引用,反之亦然,对程序的影响很小或没有除了指向相同实例的两个引用之间的比较可能会发现比指向包含相同字符的不同字符串的两个引用更快地保存相同文本这一事实之外的执行。

在大多数情况下,如果它们保存的数据相同,则应将保存不可变数据的对象视为相同。存在用于保存可变数据或用作身份令牌的对象通常应被视为彼此不同。鉴于可以定义一个EqualityComparer将视为不完全等价的等价对象的自定义(例如不区分大小写的字符串比较器),并且鉴于需要一些比严格等价更广泛的等价定义的代码通常应该知道它的类型正在使用以及什么等价定义是合适的,通常最好将Object.Equals报告对象设置为不同的,除非它们被设计为可替代的(例如,字符串)。

用现实世界的类比,假设给一个人两张纸,每张纸上都写着一个车辆识别号,并被问到第一张纸识别的汽车是否与第二张纸识别的汽车相同. 如果两张纸条的 VIN 相同,那么很明显,第一张识别的汽车与第二张识别的汽车相同。但是,如果它们具有不同的 VIN,排除了汽车具有多个 VIN 的任何奇怪可能性,那么它们会识别出不同的汽车。即使汽车具有相同的品牌和型号、选项包、油漆方案等,它们仍然是不同的汽车。购买了一个的人将无权任意开始使用另一个。有时了解两辆车目前是否具有相同的选项包等可能很有用,但如果那样的话'

于 2013-02-12T20:34:02.357 回答
0

猜想:目前的行为Object.Equals并不是大多数人认为的“平等”。

这种方法存在的主要(唯一?)原因是允许通过伪装成“==”实现来搜索集合中的项目。因此,在大多数实际情况下,此实现的行为出乎意料(除了您想要查找特定实例是否已经在集合中的情况)并且您强制为您提供自定义比较函数......

由于技术原因,它可能是 Object 的方法。Equal即对于数组/字典,假设所有对象都具有/GetHash而不是检查对象上的某些内容以启用“查找”功能可能会更快。

可以说它根本不应该在 Object 上,而只需要可以存储在集合中的类来实现某种形式的IComparable接口。

于 2013-02-06T02:45:32.200 回答