¿Por qué (objeto) 0 == (objeto) 0 es diferente de ((objeto) 0) .Equals ((objeto) 0)?

117

¿Por qué las siguientes expresiones son diferentes?

[1]  (object)0 == (object)0 //false
[2]  ((object)0).Equals((object)0) // true

De hecho, puedo entender totalmente [1] porque probablemente el tiempo de ejecución de .NET será boxel entero y comenzará a comparar las referencias. Pero, ¿por qué [2] es diferente?

André Pena
fuente
36
Bien, ahora que comprende la respuesta a esta pregunta, verifique su comprensión prediciendo el resultado de: short myShort = 0; int myInt = 0; Console.WriteLine("{0}{1}{2}", myShort.Equals(myInt), myInt.Equals(myShort), myInt == myShort); Ahora compárelo con la realidad. ¿Tu predicción fue correcta? Si no es así, ¿puede explicar la discrepancia?
Eric Lippert
1
@Star, para leer la lectura recomendada, consulte msdn.microsoft.com/en-us/library/vstudio/… para conocer las sobrecargas disponibles en el método int16también conocido como shortEquals, luego mire msdn.microsoft.com/en-us/library/ms173105.aspx . No quiero estropear el acertijo de Eric Lippert, pero debería ser bastante fácil de resolver una vez que lea esas páginas.
Sam Skuce
2
Pensé que esta es una pregunta de Java; al menos antes de ver la 'E' en Equals.
seteropere
4
@seteropere Java es realmente diferente: el autoboxing en Java almacena objetos en caché, por lo que se ((Integer)0)==((Integer)0)evalúa como verdadero.
Julio
1
También puedes probar IFormattable x = 0; bool test = (object)x == (object)x;. No se realiza ningún nuevo boxeo cuando la estructura ya está en una caja.
Jeppe Stig Nielsen

Respuestas:

151

La razón por la que las llamadas se comportan de manera diferente es que se unen a métodos muy diferentes.

El ==caso se vinculará al operador de igualdad de referencia estática. Se intcrean 2 valores en recuadros independientes , por lo que no son la misma referencia.

En el segundo caso, se vincula al método de instancia Object.Equals. Este es un método virtual que filtrará y buscará Int32.Equalsun entero en caja. Ambos valores enteros son 0, por lo tanto, son iguales

JaredPar
fuente
El ==caso no llama Object.ReferenceEquals. Simplemente produce la ceqinstrucción IL para realizar una comparación de referencia.
Sam Harwell
8
@ 280Z28 ¿No es eso solo porque el compilador lo integra?
markmnl
@ 280Z28 ¿Entonces? Un caso similar es que su método Boolean.ToString aparentemente contiene cadenas codificadas dentro de su función, en lugar de devolver los Boolean.TrueString y Boolean.FalseString expuestos públicamente. Es irrelevante; El punto es que ==hace lo mismo que ReferenceEquals(en Object, de todos modos). Todo es optimización interna en el lado de MS para evitar llamadas de función internas innecesarias en funciones de uso frecuente.
Nyerguds
6
La Especificación del lenguaje C #, párrafo 7.10.6, dice: Los operadores de igualdad de tipo de referencia predefinidos son: bool operator ==(object x, object y); bool operator !=(object x, object y);Los operadores devuelven el resultado de comparar las dos referencias en busca de igualdad o no igualdad. No es un requisito que System.Object.ReferenceEqualsse utilice el método para determinar el resultado. Para @markmnl: No, el compilador de C # no está integrado, eso es algo que el jitter a veces hace (pero no en este caso). Entonces 280Z28 tiene razón, el ReferenceEqualsmétodo no se usa realmente.
Jeppe Stig Nielsen
@JaredPar: Es interesante que la especificación diga eso, ya que no es así como se comporta realmente el lenguaje. Teniendo en cuenta los operadores definidos como anteriormente, y las variables Cat Whiskers; Dog Fido; IDog Fred;(por interfaces no relacionados ICaty IDogclases y no relacionados Cat:ICaty Dog:IDog), las comparaciones Whiskers==Fidoy Whiskers==34sería legal (el primero sólo podía ser cierto si las barbas y Fido eran tanto nula, y la segunda nunca podría ser verdad ). De hecho, un compilador de C # rechazará ambos. Whiskers==Fred;estará prohibido si Catestá sellado, pero permitido si no lo está.
supercat
26

Cuando lanza el valor int 0(o cualquier otro tipo de valor) a object, el valor está en un cuadro . Cada lanzamiento objectproduce una caja diferente (es decir, una instancia de objeto diferente). El ==operador del objecttipo realiza una comparación de referencia, por lo que devuelve falso, ya que el lado izquierdo y el lado derecho no son la misma instancia.

Por otro lado, cuando usa Equals, que es un método virtual, usa la implementación del tipo en caja real, es decir Int32.Equals, que devuelve verdadero ya que ambos objetos tienen el mismo valor.

Thomas Levesque
fuente
18

El ==operador, al ser estático, no es virtual. Ejecutará el código exacto que objectdefine la clase (siendo el objeto el tipo de tiempo de compilación de los operandos), que hará una comparación de referencia, independientemente del tipo de tiempo de ejecución de cualquiera de los objetos.

El Equalsmétodo es un método de instancia virtual. Ejecutará el código definido en el tipo de tiempo de ejecución real del (primer) objeto, no el código de la objectclase. En este caso, el objeto es un int, por lo que realizará una comparación de valores, ya que eso es lo que intdefine el tipo para su Equalsmétodo.

Servy
fuente
El ==token en realidad representa dos operadores, uno de los cuales se puede sobrecargar y el otro no. El comportamiento del segundo operador es muy diferente al de una sobrecarga en (objeto, objeto).
supercat
13

El Equals()método es virtual.
Por lo tanto, siempre llama a la implementación concreta, incluso cuando el sitio de llamadas está dirigido a object. intanula la Equals()comparación por valor, por lo que obtiene una comparación de valor.

SLaks
fuente
10

== Utilizar: Object.ReferenceEquals

Object.Equals compara el valor.

El object.ReferenceEqualsmétodo compara referencias. Cuando asigna un objeto, recibe una referencia que contiene un valor que indica su ubicación de memoria además de los datos del objeto en el montón de memoria.

El object.Equalsmétodo compara el contenido de los objetos. Primero verifica si las referencias son iguales, al igual que object.ReferenceEquals. Pero luego llama a los métodos Equals derivados para probar más la igualdad. Mira esto:

   System.Object a = new System.Object();
System.Object b = a;
System.Object.ReferenceEquals(a, b);  //returns true

fuente
Aunque se Object.ReferenceEqualscomporta como un método que usa el ==operador de C # en sus operandos, el operador de operador de igualdad de referencias de C # (que se representa mediante el uso ==de tipos de operandos para los que no se define una sobrecarga) usa una instrucción especial en lugar de llamar ReferenceEquals. Además, Object.ReferenceEqualsaceptará operandos que solo podrían coincidir si ambos resultan ser nulos, y aceptará operandos que deben ser coaccionados por tipo Objecty, por lo tanto, no podrían coincidir con nada, mientras que la versión de igualdad de referencia de ==se negaría a compilar dicho uso. .
supercat
9

El operador de C # usa el token ==para representar dos operadores diferentes: un operador de comparación sobrecargable estáticamente y un operador de comparación de referencia no sobrecargable. Cuando encuentra el ==token, primero verifica si existe alguna sobrecarga de prueba de igualdad que sea aplicable a los tipos de operandos. Si es así, invocará esa sobrecarga. De lo contrario, comprobará si los tipos son aplicables al operador de comparación de referencias. Si es así, utilizará ese operador. Si ningún operador es aplicable a los tipos de operandos, la compilación fallará.

El código (Object)0no se limita a convertir un Int32to Object: Int32como todos los tipos de valor, en realidad representa dos tipos, uno de los cuales describe valores y ubicaciones de almacenamiento (como el cero literal), pero no se deriva de nada, y uno de los cuales describe amontonar objetos y se deriva de Object; debido a que solo se puede convertir al último tipo Object, el compilador debe crear un nuevo objeto de montón de ese último tipo. Cada invocación de (Object)0crea un nuevo objeto de pila, por lo que los dos operandos ==son objetos diferentes, cada uno de los cuales, de forma independiente, encapsula el Int32valor 0.

La clase Objectno tiene ninguna sobrecarga utilizable definida para el operador igual. En consecuencia, el compilador no podrá utilizar el operador de prueba de igualdad sobrecargado y volverá a utilizar la prueba de igualdad de referencia. Debido a que los dos operandos se ==refieren a objetos distintos, informará false. La segunda comparación tiene éxito porque pregunta a una instancia de objeto de montón Int32si es igual a la otra. Como esa instancia sabe lo que significa ser igual a otra instancia distinta, puede responder true.

Super gato
fuente
Además, cada vez que escribe un literal 0en su código, asumo que crea un objeto int en el montón para eso. No es una referencia única a un valor cero estático global (como la forma en que hicieron String.Empty para evitar crear nuevos objetos de cadena vacíos solo para inicializar nuevas cadenas) Así que estoy bastante seguro de que incluso hacer un 0.ReferenceEquals(0)devolverá falso, ya que ambos 0 son Int32objetos recién creados .
Nyerguds
1
@Nyerguds, estoy bastante seguro de que todo lo que dijo es incorrecto, acerca de las entradas, el montón, el historial, la estática global, etc. 0.ReferenceEquals(0), fallará porque está intentando llamar a un método en una constante de tiempo de compilación. no hay ningún objeto para colgarlo. Un int sin caja es una estructura, almacenada en la pila. Incluso int i = 0; i.ReferenceEquals(...)no funcionará. Porque System.Int32NO hereda de Object.
Andrew Backer
@AndrewBacker, System.Int32es un struct, un structes System.ValueType, que en sí mismo hereda System.Object. Por eso hay un ToString()método y un Equalsmétodo paraSystem.Int32
Sebastián
1
Sin embargo, Nyerguds se equivoca al afirmar que se creará un Int32 en el montón, lo que no es el caso.
Sebastian
@SebastianGodelet, estoy ignorando los aspectos internos de eso. El propio System.Int32 implementa esos métodos. GetType () es externo Object, y ahí es donde dejé de preocuparme por eso hace mucho tiempo. Nunca fue necesario ir más lejos. AFAIK the CLR maneja los dos tipos de manera diferente, y especialmente. No es solo herencia. Sin isembargo, es uno de los dos tipos de datos. Simplemente no quería que nadie leyera ese comentario y se desviara tanto, incluida la extrañeza de las cadenas vacías que ignora la práctica de cadenas.
Andrew Backer
3

Ambos controles son diferentes. El primero comprueba la identidad , el segundo la igualdad . En general, dos términos son idénticos si se refieren al mismo objeto. Esto implica que son iguales. Dos términos son iguales, si sus valores son iguales.

En términos de programación, la identidad suele estar dividida por la igualdad de referencias. Si el puntero a ambos términos es igual (!), El objeto al que apuntan es exactamente el mismo. Sin embargo, si los punteros son diferentes, el valor de los objetos a los que apuntan puede ser igual. En C #, la identidad se puede verificar usando el Object.ReferenceEqualsmiembro estático , mientras que la igualdad se verifica usando el Object.Equalsmiembro no estático . Dado que está lanzando dos enteros a objetos (lo que se llama "boxeo", por cierto), el operador ==de objectrealiza la primera verificación, que está asignada por defecto Object.ReferenceEqualsy verifica la identidad. Si llama explícitamente al Equalsmiembro no estático , el envío dinámico da como resultado una llamada a Int32.Equals, que verifica la igualdad.

Ambos conceptos son similares, pero no iguales. Pueden parecer confusos al principio, ¡pero la pequeña diferencia es muy importante! Imagínese dos personas, a saber, "Alice" y "Bob". Ambos viven en una casa amarilla. Basado en la suposición de que Alice y Bob viven en un distrito, donde las casas solo difieren en su color, ambos podrían vivir en diferentes casas amarillas. Si comparas ambas casas, reconocerás que son absolutamente iguales, ¡porque ambas son amarillas! Sin embargo, no comparten la misma casa y, por lo tanto, sus casas son iguales , pero no idénticas . La identidad implicaría que viven en la misma casa.

Nota : algunos idiomas están definiendo al ===operador para verificar la identidad.

Carsten
fuente