¿Los operadores string.Equals () y == son realmente iguales?

¿Son realmente lo mismo? 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, tanto s como tvi.Header contienen “Categoría”, pero == devuelve falso e Equals() devuelve verdadero.

s se define como cadena, tvi.Header es en realidad WPF TreeViewItem.Header . Entonces, ¿por qué están devolviendo resultados diferentes? Siempre pensé que eran intercambiables en C #.

¿Alguien puede explicar por qué es esto?

Dos diferencias:

  • Equals es polimórfico (es decir, puede anularse, y la implementación utilizada dependerá del tipo de tiempo de ejecución del objeto de destino), mientras que la implementación de == utilizado se determina en función de los tipos de tiempo de comstackció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 saldrán si lo llamas en nulo, == no lo hará

     string x = null; string y = null; if (x.Equals(y)) // Bang if (x == y) // Yes 

Tenga en cuenta que puede evitar que este último sea un problema al usar 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 se llama a la función Equals en un objeto de string , y en el otro caso al operador == se llama en el tipo System.Object . string y object implementan la igualdad de forma diferente entre sí (valor frente a referencia, respectivamente).

Más allá de este hecho, cualquier tipo puede definir == y Equals differently, por lo que, en general, no son intercambiables.

Aquí hay un ejemplo que usa el 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 

Él continúa diciendo que el double.Equals(double) 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 los tipos de coma flotante.

En el caso específico de determinar la igualdad de cadenas, la preferencia de la industria es no usar ni == ni string.Equals(string) mayor parte del tiempo. Estos métodos determinan si dos cadenas son del 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 usar 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 podría ser muy difícil depurarlas si se usa una igualdad ingenua (ordinal).

C # tiene dos conceptos “iguales”: Equals y Equals ReferenceEquals . Para la mayoría de las clases que encontrará, el operador == usa una u otra (o ambas), y generalmente solo prueba para ReferenceEquals cuando maneja tipos de referencia (pero la string Class es una instancia donde C # ya sabe cómo probar la igualdad de valores) .

  • Equals compara valores. (Aunque dos variables int separadas no existen en el mismo lugar en la memoria, todavía 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 propiedad Header del TreeViewItem está TreeViewItem estáticamente para que sea de tipo object .

Por lo tanto, == cede false . Puede 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 al usar == realmente obtienes la respuesta true en diferentes instancias de cadenas con el mismo valor:

 string a = "Hell"; string b = "Hello"; a = a + "o"; Console.WriteLine(a == b); 

Como puede ver, a y b deben ser instancias de cadenas diferentes, pero como las cadenas son inmutables, el tiempo de ejecución usa el llamado interning de cadena para permitir que tanto a como b referencia a la misma cadena en la memoria. El operador == para objetos verifica la referencia, y como tanto a como b referencia a la misma instancia, el resultado es true . Cuando cambia cualquiera de ellos, se crea una nueva instancia de cadena, que es la razón por la cual es posible el internamiento de cadena.

Por cierto, la respuesta de Jon Skeet no está completa. De hecho, x == y es false pero eso solo se debe a que compara objetos y objetos por referencia. Si escribes (string)x == (string)y , volverá a ser true nuevamente. Entonces, las cadenas tienen su operador == – sobrecargado, que llama a String.Equals debajo.

Aquí hay muchas respuestas descriptivas, así que no voy a repetir lo que ya se dijo. Lo que me gustaría agregar es el siguiente código que demuestra todas las permutaciones que se me ocurren. El código es bastante largo debido a la cantidad de combinaciones. Siéntase libre de colocarlo en MSTest y ver el resultado usted mismo (la salida se incluye en la parte inferior).

Esta evidencia respalda la respuesta de Jon Skeet.

Código:

 [TestMethod] public void StringEqualsMethodVsOperator() { string s1 = new StringBuilder("string").ToString(); string s2 = new StringBuilder("string").ToString(); Debug.WriteLine("string a = \"string\";"); Debug.WriteLine("string b = \"string\";"); TryAllStringComparisons(s1, s2); s1 = null; s2 = null; Debug.WriteLine(string.Join(string.Empty, Enumerable.Repeat("-", 20))); Debug.WriteLine(string.Empty); Debug.WriteLine("string a = null;"); Debug.WriteLine("string b = null;"); TryAllStringComparisons(s1, s2); } private void TryAllStringComparisons(string s1, string s2) { Debug.WriteLine(string.Empty); Debug.WriteLine("-- string.Equals --"); Debug.WriteLine(string.Empty); Try((a, b) => string.Equals(a, b), s1, s2); Try((a, b) => string.Equals((object)a, b), s1, s2); Try((a, b) => string.Equals(a, (object)b), s1, s2); Try((a, b) => string.Equals((object)a, (object)b), s1, s2); Debug.WriteLine(string.Empty); Debug.WriteLine("-- object.Equals --"); Debug.WriteLine(string.Empty); Try((a, b) => object.Equals(a, b), s1, s2); Try((a, b) => object.Equals((object)a, b), s1, s2); Try((a, b) => object.Equals(a, (object)b), s1, s2); Try((a, b) => object.Equals((object)a, (object)b), s1, s2); Debug.WriteLine(string.Empty); Debug.WriteLine("-- a.Equals(b) --"); Debug.WriteLine(string.Empty); Try((a, b) => a.Equals(b), s1, s2); Try((a, b) => a.Equals((object)b), s1, s2); Try((a, b) => ((object)a).Equals(b), s1, s2); Try((a, b) => ((object)a).Equals((object)b), s1, s2); Debug.WriteLine(string.Empty); Debug.WriteLine("-- a == b --"); Debug.WriteLine(string.Empty); Try((a, b) => a == b, s1, s2); #pragma warning disable 252 Try((a, b) => (object)a == b, s1, s2); #pragma warning restre 252 #pragma warning disable 253 Try((a, b) => a == (object)b, s1, s2); #pragma warning restre 253 Try((a, b) => (object)a == (object)b, s1, s2); } public void Try(Expression> tryFunc, T1 in1, T2 in2) { T3 out1; Try(tryFunc, e => { }, in1, in2, out out1); } public bool Try(Expression> tryFunc, Action catchFunc, T1 in1, T2 in2, out T3 out1) { bool success = true; out1 = default(T3); try { out1 = tryFunc.Compile()(in1, in2); Debug.WriteLine("{0}: {1}", tryFunc.Body.ToString(), out1); } catch (Exception ex) { Debug.WriteLine("{0}: {1} - {2}", tryFunc.Body.ToString(), ex.GetType().ToString(), ex.Message); success = false; catchFunc(ex); } return success; } 

Salida:

 string a = "string"; string b = "string"; -- string.Equals -- Equals(a, b): True Equals(Convert(a), b): True Equals(a, Convert(b)): True Equals(Convert(a), Convert(b)): True -- object.Equals -- Equals(a, b): True Equals(Convert(a), b): True Equals(a, Convert(b)): True Equals(Convert(a), Convert(b)): True -- a.Equals(b) -- a.Equals(b): True a.Equals(Convert(b)): True Convert(a).Equals(b): True Convert(a).Equals(Convert(b)): True -- a == b -- (a == b): True (Convert(a) == b): False (a == Convert(b)): False (Convert(a) == Convert(b)): False -------------------- string a = null; string b = null; -- string.Equals -- Equals(a, b): True Equals(Convert(a), b): True Equals(a, Convert(b)): True Equals(Convert(a), Convert(b)): True -- object.Equals -- Equals(a, b): True Equals(Convert(a), b): True Equals(a, Convert(b)): True Equals(Convert(a), Convert(b)): True -- a.Equals(b) -- a.Equals(b): System.NullReferenceException - Object reference not set to an instance of an object. a.Equals(Convert(b)): System.NullReferenceException - Object reference not set to an instance of an object. Convert(a).Equals(b): System.NullReferenceException - Object reference not set to an instance of an object. Convert(a).Equals(Convert(b)): System.NullReferenceException - Object reference not set to an instance of an object. -- a == b -- (a == b): True (Convert(a) == b): True (a == Convert(b)): True (Convert(a) == Convert(b)): True 

Está claro que tvi.header no es una String . El == es un operador que está sobrecargado por String clase String , lo que significa que solo funcionará si el comstackdor sabe que ambos lados del operador son String .

Un objeto está definido por un OBJECT_ID, que es único. Si A y B son objetos y A == B es verdadero, entonces son el mismo objeto, tienen los mismos datos y métodos, pero esto también es cierto:

A.OBJECT_ID == B.OBJECT_ID

si A.Equals (B) es verdadero, eso significa que los dos objetos están en el mismo estado, pero esto no significa que A es lo mismo que B.

Las cadenas son objetos.

Tenga en cuenta que los operadores == e Igual son reflexivos, simétricos, tranzitivos, por lo que son relaciones equivalentes (para usar términos algebraicos relacionales)

Qué significa esto: si A, B y C son objetos, entonces:

(1) A == A es siempre verdadero; A.Equals (A) siempre es verdadero (reflexividad)

(2) si A == B luego B == A; Si A.Equals (B) luego B.Equals (A) (simetría)

(3) si A == B y B == C, entonces A == C; si A.Equals (B) y B.Equals (C) entonces A.Equals (C) (tranzitivity)

Además, puedes notar que esto también es cierto:

(A == B) => (A.Equals (B)), pero el inverso no es verdadero.

 AB => 0 0 1 0 1 1 1 0 0 1 1 1 

Ejemplo de vida real: dos hamburguesas del mismo tipo tienen las mismas propiedades: son objetos de la clase Hamburger, sus propiedades son exactamente las mismas, pero son entidades diferentes. Si compras estas dos hamburguesas y comes una, la otra no se comerá. Entonces, la diferencia entre Equals y ==: tienes hamburger1 y hamburger2. Están exactamente en el mismo estado (el mismo peso, la misma temperatura, el mismo sabor), por lo que hamburger1.Equals (hamburger2) es verdadero. Pero hamburger1 == hamburger2 es falso, porque si el estado de hamburger1 cambia, el estado de hamburger2 no cambia necesariamente y viceversa.

Si tú y un amigo obtienen una hamburguesa, que es la tuya y la suya al mismo tiempo, entonces debes decidir dividir la hamburguesa en dos partes, porque you.getHamburger () == friend.getHamburger () es verdadero y si esto sucede : friend.eatHamburger (), entonces tu hamburguesa también se comerá.

Podría escribir otros matices sobre Equals y ==, pero tengo hambre, así que tengo que irme.

Un saludo, Lajos Arpad.