¿Son realmente iguales los operadores string.Equals() y ==? [duplicar]
¿Son realmente iguales? Hoy me encontré con este problema. Aquí está el volcado de la ventana Inmediato:
?s
"Category"
?tvi.Header
"Category"
?s == tvi.Header
false
?s.Equals(tvi.Header)
true
?s == tvi.Header.ToString()
true
Entonces, ambos s
contienen tvi.Header
"Categoría", pero ==
devuelven falso y Equals()
verdadero.
s
se define como una cadena, tvi.Header
en realidad es un WPF TreeViewItem.Header
. Entonces, ¿por qué arrojan resultados diferentes? Siempre pensé que eran intercambiables en C#.
¿Alguien puede explicar por qué es esto?
Dos diferencias:
Equals
es polimórfico (es decir, se puede anular y la implementación utilizada dependerá del tipo de tiempo de ejecución del objeto de destino), mientras que la implementación de==
used se determina en función de los tipos de tiempo de compilación de los objetos:// Avoid getting confused by interning object x = new StringBuilder("hello").ToString(); object y = new StringBuilder("hello").ToString(); if (x.Equals(y)) // Yes // The compiler doesn't know to call ==(string, string) so it generates // a reference comparision instead if (x == y) // No string xs = (string) x; string ys = (string) y; // Now *this* will call ==(string, string), comparing values appropriately if (xs == ys) // Yes
Equals
lanzará una excepción si lo llamas en nulo, == no lo harástring x = null; string y = null; if (x.Equals(y)) // NullReferenceException if (x == y) // Yes
Tenga en cuenta que puede evitar que esto último sea un problema utilizando object.Equals
:
if (object.Equals(x, y)) // Fine even if x or y is null
Las aparentes contradicciones que aparecen en la pregunta se deben a que en un caso la Equals
función se llama sobre un string
objeto y en el otro caso el ==
operador se llama sobre el System.Object
tipo. string
e object
implementar la igualdad de manera diferente entre sí (valor versus referencia respectivamente).
Más allá de este hecho, cualquier tipo puede definirse ==
y Equals
de forma diferente, por lo que en general no son intercambiables.
A continuación se muestra un ejemplo double
(de la nota de Joseph Albahari al §7.9.2 de la especificación del lenguaje C#):
double x = double.NaN;
Console.WriteLine (x == x); // False
Console.WriteLine (x != x); // True
Console.WriteLine (x.Equals(x)); // True
Continúa diciendo que el double.Equals(double)
método fue diseñado para funcionar correctamente con listas y diccionarios. El ==
operador, por otro lado, fue diseñado para seguir el estándar IEEE 754 para tipos de punto flotante.
En el caso específico de determinar la igualdad de cadenas, la preferencia de la industria es no usar ==
ni string.Equals(string)
la mayor parte del tiempo. Estos métodos determinan si dos cadenas tienen el mismo carácter por carácter, lo que rara vez es el comportamiento correcto. Es mejor utilizar string.Equals(string, StringComparison)
, que le permite especificar un tipo particular de comparación. Al utilizar la comparación correcta, puede evitar muchos errores potenciales (muy difíciles de diagnosticar).
Aquí hay un ejemplo:
string one = "Caf\u00e9"; // U+00E9 LATIN SMALL LETTER E WITH ACUTE
string two = "Cafe\u0301"; // U+0301 COMBINING ACUTE ACCENT
Console.WriteLine(one == two); // False
Console.WriteLine(one.Equals(two)); // False
Console.WriteLine(one.Equals(two, StringComparison.InvariantCulture)); // True
Ambas cadenas en este ejemplo tienen el mismo aspecto ("Café"), por lo que esto podría ser muy difícil de depurar si se utiliza una igualdad ingenua (ordinal).
C# tiene dos conceptos "iguales": Equals
y ReferenceEquals
. Para la mayoría de las clases que encontrará, el ==
operador usa una u otra (o ambas) y generalmente solo realiza pruebas ReferenceEquals
cuando se manejan tipos de referencia (pero la string
Clase es una instancia en la que C# ya sabe cómo probar la igualdad de valores).
Equals
compara valores. (Aunque dosint
variables separadas no existen en el mismo lugar de la memoria, aún pueden contener el mismo valor).ReferenceEquals
compara la referencia y devuelve si los operandos apuntan al mismo objeto en la memoria.
Código de ejemplo:
var s1 = new StringBuilder("str");
var s2 = new StringBuilder("str");
StringBuilder sNull = null;
s1.Equals(s2); // True
object.ReferenceEquals(s1, s2); // False
s1 == s2 // True - it calls Equals within operator overload
s1 == sNull // False
object.ReferenceEquals(s1, sNull); // False
s1.Equals(sNull); // Nono! Explode (Exception)
La Header
propiedad de TreeViewItem
está tipificada estáticamente para ser de tipo object
.
Por lo tanto los ==
rendimientos false
. Puedes reproducir esto con el siguiente fragmento simple:
object s1 = "Hallo";
// don't use a string literal to avoid interning
string s2 = new string(new char[] { 'H', 'a', 'l', 'l', 'o' });
bool equals = s1 == s2; // equals is false
equals = string.Equals(s1, s2); // equals is true
Además de la respuesta de Jon Skeet , me gustaría explicar por qué la mayoría de las veces, cuando se usa, ==
se obtiene la respuesta true
en diferentes instancias de cadena con el mismo valor:
string a = "Hell";
string b = "Hello";
a = a + "o";
Console.WriteLine(a == b);
Como puede ver, a
deben b
ser instancias de cadena diferentes, pero debido a que las cadenas son inmutables, el tiempo de ejecución utiliza el llamado internamiento de cadenas para permitir que ambas a
hagan b
referencia a la misma cadena en la memoria. El ==
operador de objetos verifica la referencia y, dado que ambos a
y b
hacen referencia a la misma instancia, el resultado es true
. Cuando cambia cualquiera de ellos, se crea una nueva instancia de cadena, razón por la cual es posible la internación de cadenas.
Por cierto, la respuesta de Jon Skeet no está completa. De hecho, x == y
lo es , false
pero eso es sólo porque está comparando objetos y los objetos se comparan por referencia. Si escribes (string)x == (string)y
, volverá true
de nuevo. Entonces las cadenas tienen su operador == - sobrecargado, que llama String.Equals
debajo.