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.