以下表达式为什么不同?
[1] (object)0 == (object)0 //false
[2] ((object)0).Equals((object)0) // true
实际上,我完全可以理解[1],因为可能.NET运行时会将整数装箱
,从而开始比较引用。但是,为什么[2]不同呢?
其实,我完全理解[1],因为可能.NET运行时会将整数装箱
,导致开始比较引用。但是,为什么[2]不同呢?
这些调用表现不同的原因在于它们绑定到非常不同的方法。
==
情况将绑定到静态引用相等运算符。由此创建了2个独立的装箱 int
值,因此它们不是同一个引用。
在第二种情况下,您将绑定到实例方法 Object.Equals
。这是一个虚拟方法,它将过滤到 Int32.Equals
并检查是否有一个装箱整数。两个整数值都是0,因此它们是相等的。
==
情况并不会调用 Object.ReferenceEquals
。它只是生成 ceq
IL 指令来执行引用比较。 - Sam Harwell==
与 ReferenceEquals
(至少在 Object 上)执行相同的操作。这只是 MS 内部优化的一部分,以避免在经常使用的函数上进行不必要的内部函数调用。 - Nyergudsbool operator ==(object x, object y);
bool operator !=(object x, object y);
这些运算符返回比较两个引用是否相等或不相等的结果。并不要求使用System.Object.ReferenceEquals
方法来确定结果。至于@markmnl的问题:不,C#编译器不会执行内联优化,这是即时编译器有时执行的操作(但在这种情况下不会)。所以280Z28是正确的,ReferenceEquals
方法实际上并没有被使用。 - Jeppe Stig NielsenCat Whiskers; Dog Fido; IDog Fred;
(对于非相关接口ICat
和IDog
以及非相关类Cat:ICat
和Dog:IDog
),比较Whiskers==Fido
和Whiskers==34
是合法的(如果Whiskers和Fido都为空,则第一个比较只能为真;第二个比较永远不可能为真)。事实上,C#编译器将拒绝这两个比较。如果Cat
是密封的,则Whiskers==Fred;
将被禁止,但如果它没有被密封,则允许。 - supercat0
(或任何其他值类型)转换为object
时,该值会被装箱。每次向object
进行强制转换都会产生一个不同的盒子(即不同的对象实例)。object
类型的==
运算符执行引用比较,因此它返回false,因为左侧和右侧不是同一个实例。Equals
方法(这是一个虚拟方法)时,它使用实际装箱类型的实现,即Int32.Equals
,因为两个对象具有相同的值,所以它返回true。==
运算符是静态的,不是虚拟的。它将运行object
类定义的完全相同的代码(其中object
是操作数的编译时类型),这将执行引用比较,而不考虑任何对象的运行时类型。
Equals
方法是一个虚拟实例方法。它将运行在(第一个)对象的实际运行时类型中定义的代码,而不是object
类中的代码。在这种情况下,对象是一个int
,因此它将执行值比较,因为这是int
类型为其Equals
方法定义的内容。==
符号实际上代表两个运算符,其中一个可以重载,而另一个则不能。第二个运算符的行为与(object, object)上的重载非常不同。 - supercatEquals()
方法是虚拟的。
因此,即使调用方被强制转换为 object
,它也总是调用具体实现。
int
通过值进行比较来重写 Equals()
,因此您获得值比较。
==
使用:Object.ReferenceEquals
Object.Equals
比较值。
object.ReferenceEquals
方法比较引用。当您分配一个对象时,您会收到一个包含值的引用,该值指示其内存位置以及内存堆中对象的数据。
object.Equals
方法比较对象的内容。它首先检查引用是否相等,就像 object.ReferenceEquals 一样。但然后它调用派生的 Equals 方法进一步测试相等性。
请参见:
System.Object a = new System.Object();
System.Object b = a;
System.Object.ReferenceEquals(a, b); //returns true
Object.ReferenceEquals
的行为类似于在其操作数上使用 C# ==
运算符的方法,但 C# 引用相等运算符(表示为对没有定义重载的操作数类型使用 ==
)使用特殊指令而不是调用 ReferenceEquals
。此外,Object.ReferenceEquals
将接受只有当两个操作数都恰好为 null 时才能匹配的操作数,并将接受需要强制转换为 Object
的操作数,因此不能匹配任何内容,而 ==
的引用相等版本则会拒绝编译这种用法。 - supercat==
代表两种不同的运算符:可以静态重载的比较运算符和不可重载的引用比较运算符。当它遇到==
标记时,首先检查是否存在适用于操作数类型的任何等式测试重载。如果有,则调用该重载。否则,它将检查类型是否适用于引用比较运算符。如果是,则使用该运算符。如果没有运算符适用于操作数类型,则编译失败。(Object)0
不仅仅是将Int32
向上转型为Object
:Int32
,像所有值类型一样,实际上表示两种类型,一种描述值和存储位置(例如字面量零),但不从任何内容派生,另一种描述堆对象并从Object
派生;因为只有后者类型可以向上转型为Object
,所以编译器必须创建一个新的堆对象。每次调用(Object)0
都会创建一个新的堆对象,因此==
的两个操作数是不同的对象,每个对象都独立地封装Int32
值0。Object
没有定义任何可用的等于运算符重载。因此,编译器将无法使用重载的等式测试运算符,并将退回到使用引用等式测试。因为==
的两个操作数引用不同的对象,所以它会报告false
。第二个比较成功是因为它询问一个Int32
堆对象实例是否等于另一个。因为该实例知道与另一个不同实例相等意味着什么,它可以回答true
。0.ReferenceEquals(0)
会失败,因为你试图在编译时常量上调用一个方法。没有对象可以挂载它。未装箱的int是一个结构体,存储在堆栈上。即使int i = 0; i.ReferenceEquals(...)
也不起作用。因为System.Int32
不继承自Object
。 - AndrewObject
上是外部的,那就是我很久以前停止担心它的地方。没有必要再深入了解。据我所知,CLR以不同的方式和特别处理这两种类型。这不仅仅是继承。它是
两种数据类型之一。我只是不想让任何人阅读该评论并偏离轨道,包括关于空字符串的奇怪性质,这忽略了字符串插值。 - Andrew这两个检查是不同的。第一个检查的是身份,第二个检查的是相等性。一般来说,如果两个术语指的是同一个对象,则它们是相同的。这意味着它们是相等的。如果两个术语的值相同,则它们是相等的。
在编程方面,身份通常通过引用相等性来衡量。如果两个术语的指针相等(!),则它们指向的对象完全相同。但是,如果指针不同,则它们所指向的对象的值仍然可以相等。在C#中,可以使用静态的Object.ReferenceEquals
成员来检查身份,而可以使用非静态的Object.Equals
成员来检查相等性。由于您将两个整数转换为对象(顺便说一下,这称为“装箱”),因此object
的==
操作符执行第一个检查,默认情况下映射到Object.ReferenceEquals
并检查身份。如果您明确调用非静态的Equals
-成员,则动态分派会导致调用Int32.Equals
,该方法检查相等性。
short myShort = 0; int myInt = 0; Console.WriteLine("{0}{1}{2}", myShort.Equals(myInt), myInt.Equals(myShort), myInt == myShort);
现在将其与实际结果进行比较。你的预测是否正确?如果不是,你能解释其中的差异吗? - Eric Lippert((Integer)0)==((Integer)0)
的结果为true。 - JulesIFormattable x = 0; bool test = (object)x == (object)x;
。当结构体已经在盒子里时,不会执行新的装箱操作。 - Jeppe Stig Nielsen