¿Cuál es la diferencia entre == y Equals () para primitivas en C #?

180

Considera este código:

int age = 25;
short newAge = 25;
Console.WriteLine(age == newAge);  //true
Console.WriteLine(newAge.Equals(age)); //false
Console.ReadLine();

Ambos inty shortson tipos primitivos, pero una comparación con ==devuelve verdadero y una comparación con Equalsdevuelve falso.

¿Por qué?

Mohammad Zargarani
fuente
9
@OrangeDog Por favor piense en la pregunta y luego vote para cerrarla
44
Falta el obvio intento inverso:Console.WriteLine(age.Equals(newAge));
ANeves
3
El duplicado no explica este comportamiento; se trata de lo que Equals()es en general.
SLaks
37
Respondí esta pregunta exacta en el blog de Coverity hace unos días. blog.coverity.com/2014/01/13/inconsistent-equality
Eric Lippert
55
@CodesInChaos: la especificación en realidad usa el término "tipos primitivos" dos veces sin definirlo nunca; la implicación es que los tipos primitivos son tipos de valores incorporados, pero esto nunca se aclara. Le he recomendado a Mads que el término simplemente se elimine de la especificación, ya que parece crear más confusión de la que elimina.
Eric Lippert

Respuestas:

262

Respuesta corta:

La igualdad es complicada.

Respuesta detallada:

Los tipos primitivos anulan la base object.Equals(object)y devuelven verdadero si el recuadro objectes del mismo tipo y valor. (Tenga en cuenta que también funcionará para los tipos anulables; los tipos anulables no nulos siempre encajonan en una instancia del tipo subyacente).

Como newAgees a short, su Equals(object)método solo devuelve verdadero si pasa un short en caja con el mismo valor. Estás pasando un cuadro int, por lo que devuelve falso.

Por el contrario, el ==operador se define como tomar dos ints (o shortso longs).
Cuando se llama con una inty una short, el compilador convertir implícitamente el shortque inty comparar el resultado ints por valor.

Otras formas de hacerlo funcionar

Los tipos primitivos también tienen su propio Equals()método que acepta el mismo tipo.
Si escribe age.Equals(newAge), el compilador seleccionará int.Equals(int)como la mejor sobrecarga y se convertirá implícitamente shorta int. Luego regresará true, ya que este método simplemente compara la ints directamente.

shorttambién tiene un short.Equals(short)método, pero intno se puede convertir implícitamente short, por lo que no lo está llamando.

Podría forzarlo a llamar a este método con un yeso:

Console.WriteLine(newAge.Equals((short)age)); // true

Esto llamará short.Equals(short)directamente, sin boxeo. Si agees mayor que 32767, generará una excepción de desbordamiento.

También podría llamar a la short.Equals(object)sobrecarga, pero pasar explícitamente un objeto en caja para que obtenga el mismo tipo:

Console.WriteLine(newAge.Equals((object)(short)age)); // true

Al igual que la alternativa anterior, esto generará un desbordamiento si no cabe en a short. A diferencia de la solución anterior, lo encajonará shorten un objeto, perdiendo tiempo y memoria.

Código fuente:

Estos son los dos Equals()métodos del código fuente real:

    public override bool Equals(Object obj) {
        if (!(obj is Int16)) {
            return false;
        }
        return m_value == ((Int16)obj).m_value;
    }

    public bool Equals(Int16 obj)
    {
        return m_value == obj;
    }

Otras lecturas:

Ver Eric Lippert .

SLaks
fuente
3
@SLaks, si llamamos long == int, ¿ intconvertido implícitamente a la longderecha?
Selman Genç
1
Y sí, escribí todo eso sin intentarlo.
SLaks
1
Recuerde que, en el código de la pregunta, si uno cambia int age = 25;a const int age = 25;, entonces el resultado cambiará. Esto se debe inta shortque en ese caso existe una conversión implícita de a . Ver Conversiones implícitas de expresión constante .
Jeppe Stig Nielsen
2
@SLaks sí, pero la redacción de su respuesta "el valor pasado" puede interpretarse en ambos sentidos (como el valor pasado por el desarrollador o el valor que realmente pasa el CLR después de desempaquetar). Supongo que el usuario ocasional que aún no conoce las respuestas aquí lo leerá como el primero
JaredPar,
2
@ Rachel: Excepto que eso no es cierto; El operador predeterminado == compara los tipos de referencia por referencia. Para los tipos de valor y para los tipos que se sobrecargan ==, no lo hace.
SLaks
55

Porque no hay sobrecarga para short.Equalseso acepta un int. Por lo tanto, esto se llama:

public override bool Equals(object obj)
{
    return obj is short && this == (short)obj;
}

objno es un short... por lo tanto, es falso.

Simon Whitehead
fuente
12

Cuando pasas inta short's Equals, pasas object:

ingrese la descripción de la imagen aquí Entonces este pseudocódigo se ejecuta:

return obj is short && this == (short)obj;
Majid
fuente
10

==se usa para verificar una condición igual, se puede considerar como un operador (operador booleano), solo para comparar 2 cosas y aquí el tipo de datos no importa, ya que se realizaría una conversión de tipo y Equalstambién se usa para verificar condiciones iguales , pero en este caso los tipos de datos deberían ser los mismos. N Equals es un método, no un operador.

A continuación se muestra un pequeño ejemplo tomado del que proporcionó y esto aclarará brevemente la diferencia.

int x=1;
short y=1;
x==y;//true
y.Equals(x);//false

en el ejemplo anterior, X e Y tienen los mismos valores, es decir, 1, y cuando usamos ==, devolverá verdadero, ya que en el caso de ==, el compilador convierte el tipo corto en int y se da el resultado.

y cuando usamos Equals, se realiza la comparación, pero el compilador no realiza la conversión de tipos, por lo que se devuelve falso.

Chicos, por favor avíseme si me equivoco.

usuario2423959
fuente
6

En muchos contextos donde un método o argumento de operador no es del tipo requerido, el compilador de C # intentará realizar una conversión de tipo implícita. Si el compilador puede hacer que todos los argumentos satisfagan sus operadores y métodos agregando conversiones implícitas, lo hará sin quejarse, aunque en algunos casos (¡especialmente con pruebas de igualdad!) Los resultados pueden ser sorprendentes.

Además, cada tipo de valor tal como into shortrealmente describe tanto un tipo de valor como un tipo de objeto (*). Existen conversiones implícitas para convertir valores a otros tipos de valores y para convertir cualquier tipo de valor a su tipo de objeto correspondiente, pero los diferentes tipos de objetos no son implícitamente convertibles entre sí.

Si uno usa el ==operador para comparar ay shortan int, el shortse convertirá implícitamente en an int. Si su valor numérico es igual al del int, el intal que se convirtió será igual intal que se compara. Sin embargo, si se intenta utilizar el Equalsmétodo en corto para compararlo con un int, la única conversión implícita que satisfaría una sobrecarga del Equalsmétodo sería la conversión al tipo de objeto correspondiente int. Cuando shortse le pregunta si coincide con el objeto pasado, observará que el objeto en cuestión es intmás bien que un shorty, por lo tanto, concluirá que no puede ser igual.

En general, aunque el compilador no se quejará, se debe evitar comparar cosas que no son del mismo tipo; Si uno está interesado en saber si la conversión de cosas a una forma común daría el mismo resultado, debería realizar dicha conversión explícitamente. Considere, por ejemplo,

int i = 16777217;
float f = 16777216.0f;

Console.WriteLine("{0}", i==f);

Hay tres formas en que uno podría comparar una inta una float. Uno podría querer saber:

  1. ¿Coincide el floatvalor más cercano posible con intel float?
  2. ¿La parte del número entero floatcoincide con el int?
  3. Haz inty floatrepresenta el mismo valor numérico.

Si uno intenta comparar inty floatdirectamente, el código compilado responderá la primera pregunta; si eso es lo que pretendía el programador, sin embargo, estará lejos de ser obvio. Cambiar la comparación a (float)i == fdejaría en claro que se pretendía el primer significado, o (double)i == (double)fharía que el código respondiera a la tercera pregunta (y dejaría claro que eso era lo que se pretendía).

(*) Incluso si la especificación de C # considera un valor de tipo, por ejemplo, System.Int32como un objeto de tipo System.Int32, tal opinión se contradice con el requisito de que un código se ejecute en una plataforma cuya especificación considere valores y objetos que habitan universos diferentes. Además, si Tes un tipo de referencia, y xes un T, entonces una referencia de tipo Tdebería poder referirse x. Por lo tanto, si una variable vde tipo Int32contiene una Object, una referencia de tipo Objectdebería poder contener una referencia a vo su contenido. De hecho, una referencia de tipo Objectpodría apuntar a un objeto que contiene datos copiados v, pero no a vsí mismo ni a su contenido. Eso sugeriría que ningunovni su contenido es realmente un Object.

Super gato
fuente
1
the only implicit conversion which would satisfy an overload of the Equals method would be the conversion to the object type corresponding to intIncorrecto. A diferencia de Java, C # no tiene tipos primitivos y en caja separados. Está en caja objectporque esa es la única otra sobrecarga de Equals().
SLaks
La primera y la tercera pregunta son idénticas; el valor exacto ya se perdió en la conversión a float. Lanzar un floata a doubleno creará mágicamente una nueva precisión.
SLaks
@SLaks: Según la especificación de ECMA, que describe la máquina virtual en la que se ejecuta C #, cada definición de tipo de valor crea dos tipos distintos. La especificación de C # puede decir que el contenido de una ubicación de almacenamiento de tipo List<String>.Enumeratory un objeto de almacenamiento dinámico de tipo List<String>.Enumeratorson los mismos, pero la especificación de ECMA / CLI dice que son diferentes, e incluso cuando se usan en C # se comportan de manera diferente.
supercat
@SLaks: Si iy ffueron cada convertido a doubleantes de la comparación, se producirían 16777217.0 y 16777216.0, que compara como desigual. La conversión i floatproduciría 16777216.0f, comparando igual a f.
supercat
@SLaks: para un ejemplo simple de la diferencia entre los tipos de ubicación de almacenamiento y los tipos de objetos en caja, considere el método bool SelfSame<T>(T p) { return Object.ReferenceEquals((Object)p,(Object)p);}. El tipo de objeto en recuadro correspondiente a un tipo de valor puede satisfacer el tipo de parámetro a ReferenceEqualstravés de una transmisión de preservación de identidad ; sin embargo, el tipo de ubicación de almacenamiento requiere una conversión que no preserva la identidad . Si emitir un Ta Uproduce una referencia a algo diferente al original T, eso me sugeriría que a Tno es realmente un U.
supercat
5

Equals () es un método de System.Object Class
Sintaxis: Public virtual bool Equals ()
Recomendación si queremos comparar el estado de dos objetos, entonces deberíamos usar el método Equals ()

como se indicó anteriormente, las respuestas == operadores comparan los valores son los mismos.

No te confundas con ReferenceEqual

Reference Equals ()
Sintaxis: public static bool ReferenceEquals ()
Determina si la instancia de objetos especificada es de la misma instancia

Sugat Mankar
fuente
8
Esto no responde a la pregunta en absoluto.
Habla el
Los slaks no se explicaron con ejemplos, esto es básico de la pregunta anterior.
Sugat Mankar
4

Lo que debe darse cuenta es que hacer ==siempre terminará llamando a un método. La pregunta es si llama ==y Equalstermina llamando / haciendo las mismas cosas.

Con los tipos de referencia, ==siempre verificará primero si las referencias son las mismas ( Object.ReferenceEquals). Equalspor otro lado, puede anularse y verificar si algunos valores son iguales.

EDITAR: para responder svick y agregar un comentario de SLaks, aquí hay un código IL

int i1 = 0x22; // ldc.i4.s ie pushes an int32 on the stack
int i2 = 0x33; // ldc.i4.s 
short s1 = 0x11; // ldc.i4.s (same as for int32)
short s2 = 0x22; // ldc.i4.s 

s1 == i1 // ceq
i1 == s1 // ceq
i1 == i2 // ceq
s1 == s2 // ceq
// no difference between int and short for those 4 cases,
// anyway the shorts are pushed as integers.

i1.Equals(i2) // calls System.Int32.Equals
s1.Equals(s2) // calls System.Int16.Equals
i1.Equals(s1) // calls System.Int32.Equals: s1 is considered as an integer
// - again it was pushed as such on the stack)
s1.Equals(i1) // boxes the int32 then calls System.Int16.Equals
// - int16 has 2 Equals methods: one for in16 and one for Object.
// Casting an int32 into an int16 is not safe, so the Object overload
// must be used instead.
usuario276648
fuente
Entonces, ¿qué método compara dos ints con == llamada? Sugerencia: no hay un operator ==método para Int32, pero hay uno paraString .
svick
2
Esto no responde a la pregunta en absoluto.
SLaks
@SLaks: de hecho, no responde la pregunta específica sobre int y comparación corta, ya la respondiste. Todavía siento que es interesante explicar que ==no solo hace magia, sino que finalmente simplemente llama a un método (la mayoría de los programadores probablemente nunca implementaron / anularon ningún operador). Tal vez podría haber agregado un comentario a su pregunta en lugar de agregar mi propia respuesta. Siéntase libre de actualizar la suya si siente que lo que dije es relevante.
user276648
Tenga ==en cuenta que en los tipos primitivos no es un operador sobrecargado, sino una función de lenguaje intrínseca que se compila con la ceqinstrucción IL.
SLaks
3

== en primitivo

Console.WriteLine(age == newAge);          // true

En comparación primitiva, el operador == se comporta de manera bastante obvia, en C # hay muchas sobrecargas de operador == disponibles.

  • cadena == cadena
  • int == int
  • uint == uint
  • largo == largo
  • mucho mas

Por lo tanto, en este caso no hay conversión implícita de inta shortpero shorta intes posible. Entonces newAge se convierte en int y se produce una comparación que devuelve verdadero ya que ambos tienen el mismo valor. Entonces es equivalente a:

Console.WriteLine(age == (int)newAge);          // true

.Equals () en primitivo

Console.WriteLine(newAge.Equals(age));         //false

Aquí necesitamos ver qué es el método Equals (), llamamos a Equals con una variable de tipo corto. Entonces hay tres posibilidades:

  • Igual (objeto, objeto) // método estático del objeto
  • Igual (objeto) // método virtual del objeto
  • Equals (short) // Implementa IEquatable.Equals (short)

El primer tipo no es el caso aquí ya que el número de argumentos es diferente al que llamamos con un solo argumento de tipo int. El tercero también se elimina como se mencionó anteriormente, no es posible la conversión implícita de int a short. Así que aquí Equals(object)se llama el segundo tipo de . El short.Equals(object)es:

bool Equals(object z)
{
  return z is short && (short)z == this;
}

Entonces, aquí se probó la condición z is shortque es falsa ya que z es un int, por lo que devuelve falso

Aquí hay un artículo detallado de Eric Lippert

Zaheer Ahmed
fuente