O operador string.Equals () e == é realmente o mesmo?

Eles são realmente mesmo? Hoje me deparei com esse problema. Aqui está o despejo da janela imediata:

?s "Category" ?tvi.Header "Category" ?s == tvi.Header false ?s.Equals(tvi.Header) true ?s == tvi.Header.ToString() true 

Portanto, s e tvi.Header contêm “Category”, mas == retorna false e Equals() retorna true.

s é definido como string, tvi.Header é na verdade um WPF TreeViewItem.Header . Então, por que eles estão retornando resultados diferentes? Eu sempre pensei que eles eram intercambiáveis ​​em c #.

Alguém pode explicar por que isso é?

Duas diferenças:

  • Equals é polimórfico (isto é, pode ser substituído e a implementação usada dependerá do tipo de tempo de execução do object de destino), enquanto a implementação de == usada é determinada com base nos tipos de tempo de compilation dos objects:

     // 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 vai bater se você chamar nulo, == não

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

Note que você pode evitar que este seja um problema usando object.Equals :

 if (object.Equals(x, y)) // Fine even if x or y is null 

As aparentes contradições que aparecem na pergunta são causadas porque em um caso a function Equals é chamada em um object de string , e no outro caso o operador == é chamado no tipo System.Object . string e object implementam igualdade de maneira diferente entre si (valor vs. referência respectivamente).

Além desse fato, qualquer tipo pode definir == e Equals diferentemente, portanto, em geral, eles não são intercambiáveis.

Aqui está um exemplo usando o double (da nota de Joseph Albahari para §7.9.2 da especificação da linguagem C #):

 double x = double.NaN; Console.WriteLine (x == x); // False Console.WriteLine (x != x); // True Console.WriteLine (x.Equals(x)); // True 

Ele prossegue dizendo que o double.Equals(double) foi projetado para funcionar corretamente com listas e dictionarys. O operador == , por outro lado, foi projetado para seguir o padrão IEEE 754 para tipos de ponto flutuante.

No caso específico de determinar a igualdade de strings, a preferência da indústria é usar nem == nem string.Equals(string) maioria das vezes. Esses methods determinam se duas cadeias são o mesmo caractere para caractere, o que raramente é o comportamento correto. É melhor usar string.Equals(string, StringComparison) , que permite especificar um tipo específico de comparação. Usando a comparação correta, você pode evitar muitos erros potenciais (muito difíceis de diagnosticar).

Aqui está um exemplo:

 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 as strings neste exemplo têm a mesma aparência (“Café”), portanto, isso pode ser muito difícil de depurar se você usar uma igualdade ingênua (ordinal).

C # tem dois conceitos “iguais”: Equals e ReferenceEquals . Para a maioria das classs que você encontrará, o operador == usa um ou o outro (ou ambos) e geralmente testa somente o ReferenceEquals ao manipular tipos de referência (mas a class string é uma instância onde o C # já sabe como testar a igualdade de valor) .

  • Equals compara valores. (Mesmo que duas variables int separadas não existam no mesmo local na memory, elas ainda podem conter o mesmo valor.)
  • ReferenceEquals compara a referência e retorna se os operandos apontam para o mesmo object na memory.

Exemplo de código:

 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) 

A propriedade Header do TreeViewItem é estaticamente digitada para ser do tipo type.

Portanto, o == produz false . Você pode reproduzir isso com o seguinte snippet simples:

 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 

Além da resposta de Jon Skeet , gostaria de explicar por que a maior parte do tempo ao usar == você realmente obtém a resposta true em instâncias de string diferentes com o mesmo valor:

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

Como você pode ver, b devem ser instâncias de string diferentes, mas como as strings são imutáveis, o tempo de execução usa o chamado string interning para permitir que tanto a como b façam referência à mesma string na memory. O operador == para objects verifica a referência e, como tanto a como b referenciam a mesma instância, o resultado é true . Quando você altera um deles, uma nova instância de cadeia é criada, e é por isso que a internação de cadeia é possível.

By the way, a resposta de Jon Skeet não está completa. De fato, x == y é false mas isso é apenas porque ele está comparando objects e objects por referência. Se você escrever (string)x == (string)y , ele retornará true novamente. Portanto, as seqüências de caracteres têm seu operador == – sobrecarregado, que chama String.Equals abaixo.

Há muitas respostas descritivas aqui, então não vou repetir o que já foi dito. O que eu gostaria de acrescentar é o código a seguir, demonstrando todas as permutações em que posso pensar. O código é bastante longo devido ao número de combinações. Sinta-se à vontade para deixá-lo no MSTest e ver a saída para si mesmo (a saída está incluída na parte inferior).

Essa evidência apóia a resposta 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 restore 252 #pragma warning disable 253 Try((a, b) => a == (object)b, s1, s2); #pragma warning restore 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; } 

Saída:

 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 

É claro que o tvi.header não é uma String . O == é um operador que é sobrecarregado pela class String , o que significa que ele estará funcionando somente se o compilador souber que ambos os lados do operador são String .

Um object é definido por um OBJECT_ID, que é exclusivo. Se A e B são objects e A == B é verdadeiro, então eles são o mesmo object, eles têm os mesmos dados e methods, mas isso também é verdade:

A.OBJECT_ID == B.OBJECT_ID

se A.Equals (B) for verdadeiro, isso significa que os dois objects estão no mesmo estado, mas isso não significa que A é o mesmo que B.

Cordas são objects.

Note que os operadores == e Equals são reflexivos, simétricos, tranzitivos, portanto são relações equivalentes (para usar termos algébricos relacionais)

O que isto significa: Se A, B e C são objects, então:

(1) A == A é sempre verdadeiro; A.Equals (A) é sempre verdadeiro (reflexividade)

(2) se A = = B, então B = = A; Se A.Equals (B) então B.Equals (A) (simetry)

(3) se A == B e B == C, então A == C; se A.Equals (B) e B.Equals (C) então A.Equals (C) (tranzitivity)

Além disso, você pode notar que isso também é verdade:

(A == B) => (A.Equals (B)), mas o inverso não é verdadeiro.

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

Exemplo de vida real: Dois hambúrgueres do mesmo tipo têm as mesmas propriedades: são objects da class Hamburger, suas propriedades são exatamente as mesmas, mas são entidades diferentes. Se você comprar esses dois hambúrgueres e comer um, o outro não será comido. Então, a diferença entre Equals e ==: Você tem hamburger1 e hamburger2. Eles estão exatamente no mesmo estado (o mesmo peso, a mesma temperatura, o mesmo sabor), então hamburger1.Equals (hamburger2) é verdade. Mas hamburger1 == hamburger2 é falso, porque se o estado de hamburger1 muda, o estado de hamburger2 não muda necessariamente e vice-versa.

Se você e um amigo pegam um Hamburger, que é seu e dele ao mesmo tempo, então você deve dividir o Hamburger em duas partes, porque you.getHamburger () == friend.getHamburger () é verdadeiro e se isso acontecer : friend.eatHamburger (), então o seu Hamburger também será comido.

Eu poderia escrever outras nuances sobre Equals e ==, mas estou ficando com fome, então eu tenho que ir.

Atenciosamente, Lajos Arpad.