C # diferencia entre == y Equals ()

548

Tengo una condición en una aplicación de Silverlight que compara 2 cadenas, por alguna razón cuando la uso ==devuelve false mientras .Equals()devuelve true .

Aquí está el código:

if (((ListBoxItem)lstBaseMenu.SelectedItem).Content.Equals("Energy Attack"))
{
    // Execute code
}

if (((ListBoxItem)lstBaseMenu.SelectedItem).Content == "Energy Attack")
{
    // Execute code
}

¿Alguna razón de por qué sucede esto?

Drahcir
fuente
8
La cadena se anula ==, pero los operadores no son polimórficos. En este código, el ==operador se invoca en tipo object, que hace una comparación de identidad en lugar de un valor.
Drew Noakes
12
Para ampliar el comentario de @DrewNoakes: El compilador elige una ==sobrecarga basada en el tipo de tiempo de compilación de los operandos. El Contentinmueble es object. Los operadores no son virtuales, por lo que ==se llama a la implementación predeterminada de , que ofrece una comparación de igualdad de referencia. Con Equals, la llamada va al método virtual object.Equals(object); stringanula este método y realiza una comparación ordinal en el contenido de la cadena. Consulte msdn.microsoft.com/en-us/library/fkfd9eh8(v=vs.110).aspx y referencesource.microsoft.com/#mscorlib/system/string.cs,507 .
phoog
66
La explicación de @ phoog es precisa. Cabe señalar que cuando el lado izquierdo de ==tiene tipo de tiempo de compilación objecty el lado derecho tiene tipo de tiempo de compilación string, entonces el compilador de C # debe elegir la sobrecarga (problemática, en este caso) operator ==(object, object); pero se emita una advertencia de tiempo de compilación que podría ser no intencionados. ¡Entonces lea las advertencias en tiempo de compilación! Para solucionar el problema y seguir usándolo ==, eche el lado izquierdo a string. Si no recuerdo mal, el texto de advertencia sugiere exactamente eso.
Jeppe Stig Nielsen
1
@JeppeStigNielsen +1 por los consejos para leer las advertencias del compilador. Aún mejor: active la opción de advertencias como errores para obligar a todos a prestarles atención.
phoog

Respuestas:

429

Cuando ==se usa en una expresión de tipo object, se resolverá System.Object.ReferenceEquals.

Equalses solo un virtualmétodo y se comporta como tal, por lo que se utilizará la versión anulada (que, por stringtipo, compara los contenidos).

Mehrdad Afshari
fuente
57
A menos que el operador se implemente específicamente en la clase
Dominic Cronin
23
@DominicCronin Esto no es cierto. Incluso si se implementa == en la clase, se ignorará porque el tipo a la izquierda de la comparación es objeto. Parece que las sobrecargas del operador se determinan en el momento de la compilación y en el momento de la compilación todo lo que sabe es que el lado izquierdo es un objeto.
MikeKulls
44
@DominicCronin Creo que su primera declaración es correcta en que == resolverá objetar, pero su segunda declaración de que las sobrecargas del operador resuelven de manera similar no lo es. Son bastante diferentes, por eso .Equals se resolverá en cadena mientras que == se resolverá en objeto.
MikeKulls
8
Para que quede claro, el objecttipo (observe la fuente monoespaciada) está técnicamente destinado a ser "una expresión de tipo System.Object". No tiene nada que ver con el tipo de tiempo de ejecución de la instancia a la que hace referencia la expresión. Creo que la afirmación "los operadores definidos por el usuario son tratados como virtualmétodos" es extremadamente engañosa. Se tratan como métodos sobrecargados y solo dependen del tipo de tiempo de compilación de los operandos. De hecho, después de calcular el conjunto de operadores candidatos definidos por el usuario, el resto del procedimiento de enlace será exactamente el algoritmo de resolución de sobrecarga del método
Mehrdad Afshari
44
@DominicCronin La parte engañosa es que la virtualresolución del método depende del tipo de tiempo de ejecución real de una instancia, mientras que eso se ignora por completo en la resolución de sobrecarga del operador, y ese es realmente el punto central de mi respuesta.
Mehrdad Afshari
314

Al comparar una referencia de objeto a una cadena (incluso si la referencia de objeto se refiere a una cadena), ==se ignora el comportamiento especial del operador específico de la clase de cadena.

Normalmente (cuando no se trata de cadenas, es decir), Equalscompara valores , mientras ==compara referencias de objetos . Si dos objetos que está comparando se refieren a la misma instancia exacta de un objeto, ambos devolverán verdadero, pero si uno tiene el mismo contenido y proviene de una fuente diferente (es una instancia separada con los mismos datos), solo Equals lo hará volver verdadero. Sin embargo, como se señaló en los comentarios, la cadena es un caso especial porque anula el ==operador, de modo que cuando se trata únicamente de referencias de cadena (y no referencias de objeto), solo se comparan los valores, incluso si son instancias separadas. El siguiente código ilustra las sutiles diferencias en los comportamientos:

string s1 = "test";
string s2 = "test";
string s3 = "test1".Substring(0, 4);
object s4 = s3;
Console.WriteLine("{0} {1} {2}", object.ReferenceEquals(s1, s2), s1 == s2, s1.Equals(s2));
Console.WriteLine("{0} {1} {2}", object.ReferenceEquals(s1, s3), s1 == s3, s1.Equals(s3));
Console.WriteLine("{0} {1} {2}", object.ReferenceEquals(s1, s4), s1 == s4, s1.Equals(s4));

El resultado es:

True True True
False True True
False False True
BlueMonkMN
fuente
8
Correcto. El operador '==' compara referencias de objetos (comparación superficial) mientras que .Equals () compara contenido de objetos (comparación profunda). Como dijo @mehrdad, .Equals () se anula para proporcionar esa comparación de contenido profunda.
Andrew
1
Dejaré la publicación aquí porque creo que es valioso enfatizar lo que no está sucediendo, ya que debes prestar mucha atención para darte cuenta. (Y creo que el código para demostrar los conocimientos correctos e incorrectos vale la pena también.) Espero que la clasificación no pueda ser inferior a 0.
BlueMonkMN
55
Seguramente String implementa un operador personalizado ==. Si no fuera así, usar == no compararía el contenido. Entonces, String es un mal ejemplo para usar aquí, ya que no nos ayuda a comprender el caso general en el que no se ha definido un operador personalizado.
Dominic Cronin
66
+1 para el ejemplo de código épico, eso me hizo entender esto. Muestra el caso general del tipo estático (tipo del lado izquierdo) como objeto y el caso específico del tipo estático (tipo / RHS) como cadena. Y toca bien en la cuerda interna.
barlop
2
@badsamaritan Debido al internamiento de cuerdas
Alexander Derck
46

==y .Equalsambos dependen del comportamiento definido en el tipo real y el tipo real en el sitio de la llamada. Ambos son solo métodos / operadores que se pueden anular en cualquier tipo y dar cualquier comportamiento que el autor desee. En mi experiencia, encuentro que es común que las personas implementen .Equalsen un objeto pero descuidan implementar el operador ==. Esto significa que en .Equalsrealidad medirá la igualdad de los valores mientras ==medirá si son o no la misma referencia.

Cuando estoy trabajando con un nuevo tipo cuya definición está en flujo o escribiendo algoritmos genéricos, la mejor práctica es la siguiente

  • Si quiero comparar referencias en C #, las uso Object.ReferenceEqualsdirectamente (no es necesario en el caso genérico)
  • Si quiero comparar valores, uso EqualityComparer<T>.Default

En algunos casos, cuando siento que el uso de ==es ambiguo, usaré explícitamente Object.Referenceiguales en el código para eliminar la ambigüedad.

Eric Lippert recientemente hizo una publicación de blog sobre el tema de por qué hay 2 métodos de igualdad en el CLR. Vale la pena leer

JaredPar
fuente
Bueno, Jared, violas directamente el famoso Jeff: "El mejor código es que no hay ningún código aquí". ¿Está esto realmente justificado? Por otro lado, puedo ver de dónde proviene esto y por qué podría ser deseable hacer explícita la semántica. Para este caso, prefiero la forma en que VB trata con la igualdad de objetos. Es breve e inequívoco.
Konrad Rudolph
@ Konrad, realmente debería haber dicho "cuando no estoy familiarizado con un tipo, creo que la mejor práctica es la siguiente". Sí, VB tiene una semántica mucho mejor aquí porque realmente separa el valor y la igualdad de referencia. C # mezcla los dos y ocasionalmente ocasiona errores de ambigüedad.
JaredPar
10
Esto no es enteramente verdad. == no se puede anular, es un método estático. Solo se puede sobrecargar, lo cual es una diferencia importante. Entonces, el código que se ejecuta para un operador == está vinculado en tiempo de compilación, mientras que Equals es virtual y se encuentra en tiempo de ejecución.
Stefan Steinegger
20

== Operador

  1. Si los operandos son tipos de valor y sus valores son iguales, devuelve verdadero más falso.
  2. Si los operandos son tipos de referencia con excepción de una cadena y ambos se refieren a la misma instancia (mismo objeto), devuelve verdadero más falso.
  3. Si los operandos son de tipo cadena y sus valores son iguales, devuelve verdadero más falso.

Igual

  1. Si los operandos son Tipos de referencia , realiza Igualdad de referencia, es decir, si ambos se refieren a la misma instancia (mismo objeto), devuelve verdadero más falso.
  2. Si los operandos son tipos de valor , a diferencia del operador ==, primero verifica su tipo y si sus tipos son iguales, realiza el operador ==, de lo contrario, devuelve falso.
kashif
fuente
2
Esto no es correcto. El ==operador puede sobrecargarse para cualquier tipo, no solo cadena. Describir una excepción de caso especial solo para cadenas tergiversa la semántica del operador. Sería más preciso, aunque tal vez no terriblemente útil, decir "si los operandos son tipos de referencia, devuelve verdadero si los operandos se refieren al mismo objeto, a menos que haya una sobrecarga aplicable, en cuyo caso la implementación de esa sobrecarga determina el resultado ". Lo mismo es cierto Equalscon la complicación adicional de que es un método virtual, por lo que su comportamiento puede anularse y sobrecargarse.
phoog
19

En primer lugar, no es una diferencia. Para números

> 2 == 2.0
True

> 2.Equals(2.0)
False

Y para cuerdas

> string x = null;
> x == null
True

> x.Equals(null)
NullReferenceException

En ambos casos, se ==comporta de manera más útil que.Equals

Coronel Panic
fuente
2
No estoy seguro de que la coerción de tipos integrales a tipos de punto flotante con el ==operador sea algo bueno. Por ejemplo, ¿debería 16777216.0f igual (int) 16777217, (doble) 16777217.0, ambos o ninguno? Las comparaciones entre tipos integrales están bien, pero las comparaciones de punto flotante solo se deben realizar en mi humilde opinión con valores explícitamente convertidos en tipos coincidentes. La comparación de a floatcon algo diferente a a float, o doublea algo diferente a a double, me parece un olor a código importante que no debería compilarse sin diagnósticos.
supercat
1
@supercat Estoy de acuerdo, es angustiante lo que x == yno implica x/3 == y/3(intente x = 5y y = 5.0).
Coronel Panic
Considero que el uso de /la división entera es un defecto en el diseño de C # y Java. Sin embargo, Pascal dive incluso VB.NET's ` are much better. The problems with == `son peores: x==yy y==zno implica eso x==z(considere los tres números en mi comentario anterior). En cuanto a la relación que sugiere, incluso si xy yson ambos floato ambos double, x.equals((Object)y)no implica que 1.0f/x == 1.0f / a '(si tuviera mis druthers, lo garantizaría; incluso si ==no distingue entre positivo y cero, Equalsdebería).
supercat
¡Eso es normal, porque el primer parámetro de Equals () es una cadena!
Whiplash
17

Por lo que yo entiendo, la respuesta es simple:

  1. == compara referencias de objetos.
  2. .Equals compara el contenido del objeto.
  3. String los tipos de datos siempre actúan como comparación de contenido.

Espero estar en lo cierto y que haya respondido a tu pregunta.

Liraz Shaka Amir
fuente
15

Agregaría que si arroja su objeto a una cadena, funcionará correctamente. Es por eso que el compilador le dará una advertencia que dice:

Posible comparación de referencia involuntaria; para obtener una comparación de valores, eche el lado izquierdo para escribir 'string'

MikeKulls
fuente
1
Exactamente. @ DominicCronin: Observe siempre las advertencias en tiempo de compilación. Si es así object expr = XXX; if (expr == "Energy") { ... }, como el lado izquierdo es del tipo de tiempo de compilación object, el compilador tiene que usar la sobrecarga operator ==(object, object). Comprueba la igualdad de referencia. Si eso dará trueo falsepuede ser difícil de predecir debido al internamiento de cadenas . Si sabe que el lado izquierdo es nullde tipo o string, escriba el lado izquierdo stringantes de usar ==.
Jeppe Stig Nielsen
para poner parte de eso de otra manera. == (para determinar si usa igualdad de referencia o igualdad de valor) depende del tipo de tiempo de compilación / tipo estático / tipo del lado izquierdo. (ese es el tipo que se resuelve en un análisis de tiempo de compilación). En lugar del tipo de tiempo de ejecución / tipo dinámico / tipo RHS. El código de BlueMonkMN muestra eso, aunque no con el casting.
barlop
5

Debido a que la versión estática del .Equalmétodo no se mencionó hasta ahora, me gustaría agregar esto aquí para resumir y comparar las 3 variaciones.

MyString.Equals("Somestring"))          //Method 1
MyString == "Somestring"                //Method 2
String.Equals("Somestring", MyString);  //Method 3 (static String.Equals method) - better

donde MyStringes una variable que viene de otra parte del código.

Información de fondo y para verano:

En Java, usar ==para comparar cadenas no debe usarse. Menciono esto en caso de que necesite usar ambos idiomas y también para hacerle saber que el uso ==también se puede reemplazar con algo mejor en C #.

En C # no hay diferencia práctica para comparar cadenas usando el Método 1 o el Método 2, siempre que ambas sean de tipo cadena. Sin embargo, si uno es nulo, uno es de otro tipo (como un número entero), o uno representa un objeto que tiene una referencia diferente, entonces, como lo muestra la pregunta inicial, puede experimentar que comparar el contenido para la igualdad no devuelva lo que tu esperas.

Solución sugerida:

Debido a que usar ==no es exactamente lo mismo que usar .Equalsal comparar cosas, puedes usar el método estático String.Equals en su lugar. De esta manera, si los dos lados no son del mismo tipo, todavía comparará el contenido y si uno es nulo, evitará la excepción.

   bool areEqual = String.Equals("Somestring", MyString);  

Es un poco más para escribir, pero en mi opinión, es más seguro de usar.

Aquí hay información copiada de Microsoft:

public static bool Equals (string a, string b);

Parámetros

a Cuerda

La primera cadena para comparar, o null.

b Cuerda

La segunda cadena para comparar, o null.

Devoluciones Boolean

truesi el valor de aes el mismo que el valor de b; de lo contrario, false. Si ambos ay bson null, el método regresa true.

Mario Levesque
fuente
5

Solo como una adición a las respuestas ya buenas: este comportamiento NO se limita a las cadenas o la comparación de diferentes tipos de números. Incluso si ambos elementos son de tipo objeto del mismo tipo subyacente. "==" no funcionará.

La siguiente captura de pantalla muestra los resultados de comparar dos objetos {int} - valores

Ejemplo de VS2017

Ole Albers
fuente
2

Estoy un poco confundido aquí. Si el tipo de contenido de tiempo de ejecución es de tipo cadena, entonces ambos == y Equals deberían devolver verdadero. Sin embargo, dado que este no parece ser el caso, entonces el tipo de contenido de tiempo de ejecución no es una cadena y llamar a Iguales es una igualdad referencial y esto explica por qué falla Equals ("Energy Attack"). Sin embargo, en el segundo caso, la decisión sobre qué operador sobrecargado == estático debería llamarse en tiempo de compilación y esta decisión parece ser == (cadena, cadena). Esto me sugiere que el Contenido proporciona una conversión implícita a cadena.

Mehmet Aras
fuente
2
Lo tienes de atrás hacia adelante. Para empezar, Equals ("Energy Attack") no falla, == es el que devuelve falso. El == falla porque está usando el == del objeto, no la cadena.
MikeKulls
Por defecto, el operador == prueba la igualdad de referencia al determinar si dos referencias indican el mismo objeto. Por lo tanto, los tipos de referencia no tienen que implementar el operador == para obtener esta funcionalidad. Cuando un tipo es inmutable, es decir, los datos contenidos en la instancia no se pueden cambiar, sobrecargar el operador == para comparar la igualdad de valores en lugar de la igualdad de referencia puede ser útil porque, como objetos inmutables, se pueden considerar iguales siempre ya que tienen el mismo valor No es una buena idea anular el operador == en tipos no inmutables.
Wajeed-MSFT
2

Hay otra dimensión a una respuesta anterior de @BlueMonkMN. La dimensión adicional es que la respuesta a la pregunta del título de @ Drahcir como se dice también depende de cómo llegamos al stringvalor. Para ilustrar:

string s1 = "test";
string s2 = "test";
string s3 = "test1".Substring(0, 4);
object s4 = s3;
string s5 = "te" + "st";
object s6 = s5;
Console.WriteLine("{0} {1} {2}", object.ReferenceEquals(s1, s2), s1 == s2, s1.Equals(s2));

Console.WriteLine("\n  Case1 - A method changes the value:");
Console.WriteLine("{0} {1} {2}", object.ReferenceEquals(s1, s3), s1 == s3, s1.Equals(s3));
Console.WriteLine("{0} {1} {2}", object.ReferenceEquals(s1, s4), s1 == s4, s1.Equals(s4));

Console.WriteLine("\n  Case2 - Having only literals allows to arrive at a literal:");
Console.WriteLine("{0} {1} {2}", object.ReferenceEquals(s1, s5), s1 == s5, s1.Equals(s5));
Console.WriteLine("{0} {1} {2}", object.ReferenceEquals(s1, s6), s1 == s6, s1.Equals(s6));

El resultado es:

True True True

  Case1 - A method changes the value:
False True True
False False True

  Case2 - Having only literals allows to arrive at a literal:
True True True
True True True
Novichok
fuente
2

Agregar un punto más a la respuesta.

.EqualsTo() El método le brinda la posibilidad de comparar con la cultura y mayúsculas y minúsculas.

Bala
fuente
0

El ==token en C # se usa para dos operadores diferentes de verificación de igualdad. Cuando el compilador encuentra ese token, verificará si alguno de los tipos comparados ha implementado una sobrecarga de operador de igualdad para los tipos de combinación específicos que se comparan (*) o para una combinación de tipos a los que ambos tipos se pueden convertir. Si el compilador encuentra una sobrecarga, la usará. De lo contrario, si los dos tipos son ambos tipos de referencia y no son clases no relacionadas (pueden ser una interfaz o pueden ser clases relacionadas), el compilador lo considerará ==como un operador de comparación de referencias. Si ninguna de las condiciones se aplica, la compilación fallará.

Tenga en cuenta que algunos otros idiomas usan tokens separados para los dos operadores de verificación de igualdad. En VB.NET, por ejemplo, el =token se usa dentro de expresiones únicamente para el operador de verificación de igualdad sobrecargable, y Isse usa como operador de prueba de referencia o de prueba nula. Un uso =en un tipo que no anula el operador de verificación de igualdad fallará, al igual que intentar usarlo Ispara cualquier propósito que no sea probar la igualdad o nulidad de referencia.

(*) Los tipos generalmente solo sobrecargan la igualdad para comparación con ellos mismos, pero puede ser útil para los tipos sobrecargar el operador de igualdad para la comparación con otros tipos particulares; por ejemplo, intpodría haber (y en mi humilde opinión debería haberlo definido, pero no lo hizo) un operador de igualdad para compararlo float, de modo que 16777217 no se reportaría igual a 16777216f. Tal como están las cosas, dado que dicho operador no está definido, C # promoverá el intto float, redondeándolo a 16777216f antes de que el operador de verificación de igualdad lo vea; ese operador luego ve dos números iguales de punto flotante y los informa como iguales, sin darse cuenta del redondeo que tuvo lugar.

Super gato
fuente
En lugar de que una comparación int-float devuelva falso, prefiero el enfoque que usa F #, que es no permitir tal comparación. Luego, el programador puede decidir si y cómo manejar el hecho de que los valores tienen un tipo diferente. Porque a veces, después de todo, hacer desea tratar 3como igual a 3.0f. Si requerimos que el programador diga lo que se pretende en cada caso, entonces no hay peligro de que el comportamiento predeterminado conduzca a resultados no deseados, ya que no existe un comportamiento predeterminado.
phoog
@phoog: Mi opinión personal es que los idiomas deben tener sus medios "normales" de prueba de igualdad para implementar una relación de equivalencia y prohibir todas las combinaciones de operandos para las que no lo haría. No veo una gran ventaja de tener un lenguaje que verifique la igualdad entre enteros y flotantes al confirmar que un flotante representa con precisión un número entero que coincide con el int, en lugar de simplemente prohibir tales comparaciones, pero consideraría cualquier enfoque superior a que el lenguaje funcione Una conversión con pérdida antes de la comparación.
supercat
0

Realmente excelentes respuestas y ejemplos!

Solo me gustaría agregar la diferencia fundamental entre los dos,

Los operadores como ==no son polimórficos, mientras que Equalsson

Con ese concepto en mente, si resuelve algún ejemplo (mirando el tipo de referencia de la mano izquierda y derecha, y verificando / sabiendo si el tipo realmente tiene == operador sobrecargado y Equals siendo anulado), seguramente obtendrá la respuesta correcta .

Manish Basantani
fuente
-2

==

El operador == puede usarse para comparar dos variables de cualquier tipo, y simplemente compara los bits .

int a = 3;
byte b = 3;
if (a == b) { // true }

Nota: hay más ceros en el lado izquierdo del int pero no nos importa eso aquí.

int a (00000011) == byte b (00000011)

Recuerde que el operador == solo se preocupa por el patrón de los bits en la variable.

Use == Si dos referencias (primitivas) se refieren al mismo objeto en el montón.

Las reglas son las mismas tanto si la variable es una referencia como si es primitiva.

Foo a = new Foo();
Foo b = new Foo();
Foo c = a;

if (a == b) { // false }
if (a == c) { // true }
if (b == c) { // false }

a == c es verdadero a == b es falso

los patrones de bits son los mismos para ayc, por lo que son iguales usando ==.

Igual():

Use el método equals () para ver si dos objetos diferentes son iguales .

Como dos objetos String diferentes que representan los personajes de "Jane"

Sanchit
fuente
2
Esto es incorrecto. Considere lo siguiente: object a = 3; object b = 3; Console.WriteLine(a == b);. La salida es falsa, aunque los patrones de bits de los valores son los mismos. Los tipos de los operandos también importan. La razón por la que "no nos importa" la cantidad diferente de ceros en su ejemplo es que cuando llamamos al operador igual, la cantidad de ceros es realmente la misma , debido a la conversión implícita.
phoog
-2

La única diferencia entre Igual y == está en la comparación del tipo de objeto. en otros casos, como los tipos de referencia y los tipos de valor, son casi iguales (ambos son igualdad de bits o ambos son igualdad de referencia).

objeto: igual: igualdad de bits ==: igualdad de referencia

cadena: (igual y == son lo mismo para la cadena, pero si una de la cadena cambia a objeto, entonces el resultado de la comparación será diferente) Igual: igualdad de bits ==: igualdad de bits

Ver aquí para más explicaciones.

Will Yu
fuente
Object.Equals no necesariamente considera la igualdad bit a bit. Es un método virtual, y una anulación puede hacer lo que quiera.
phoog
Sí, tienes razón, puedes hacer lo que quieras para anularlo. pero el tema del que hablamos es la implementación predeterminada. la implementación predeterminada de Object.Equals es la igualdad en cuanto a bits.
Will Yu