Diferença de C # entre == e Equals ()

548

Eu tenho uma condição em um aplicativo silverlight que compara duas strings, por algum motivo, quando eu o uso ==, retorna false enquanto .Equals()retorna true .

Aqui está o código:

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

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

Alguma razão para isso estar acontecendo?

Drahcir
fonte
8
Substituições de string ==, mas os operadores não são polimórficos. Nesse código, o ==operador é chamado no tipo object, que faz uma comparação de identidade em vez de um valor um.
de Drew Noakes
12
Para expandir o comentário de @DrewNoakes: O compilador escolhe uma ==sobrecarga com base no tipo de tempo de compilação dos operandos. A Contentpropriedade é object. Os operadores não são virtuais, portanto, a implementação padrão de ==é chamada, fornecendo uma comparação de igualdade de referência. Com Igual, a chamada vai para o método virtual object.Equals(object); stringsubstitui esse método e executa uma comparação ordinal no conteúdo da string. Consulte msdn.microsoft.com/en-us/library/fkfd9eh8(v=vs.110).aspx e referenceource.microsoft.com/#mscorlib/system/string.cs.507 .
Phoog
6
A explicação de @ phoog é precisa. Deve-se observar que, quando o lado esquerdo do tipo ==tem tempo de compilação objecte o lado direito string, o tipo do tempo de compilação , o compilador C # deve selecionar a sobrecarga (problemática, neste caso) operator ==(object, object); mas vai emitir um aviso de tempo de compilação que poderia ser não intencional. Então leia os avisos em tempo de compilação! Para corrigir o problema e ainda usar ==, gire o lado esquerdo para string. Se bem me lembro, o texto de aviso sugere exatamente isso.
Jeppe Stig Nielsen
1
@JeppeStigNielsen +1 para obter conselhos para ler avisos do compilador. Ainda melhor: ative a opção de avisos como erros para forçar todos a prestar atenção neles.
Phoog #

Respostas:

429

Quando ==é usado em uma expressão do tipo object, será resolvido como System.Object.ReferenceEquals.

Equalsé apenas um virtualmétodo e se comporta como tal; portanto, a versão substituída será usada (que, para o stringtipo, compara o conteúdo).

Mehrdad Afshari
fonte
57
A menos que o operador é especificamente implementado na classe
Dominic Cronin
23
@DominicCronin Isso não é verdade. Mesmo que == seja implementado na classe, ele será ignorado porque o tipo à esquerda da comparação é objeto. Parece que as sobrecargas do operador são determinadas no momento da compilação e, no momento da compilação, tudo o que sabemos é que o lado esquerdo é um objeto.
21412 MikeKulls
4
@DominicCronin Acredito que sua primeira declaração esteja correta, pois == resolverá objetar, mas sua segunda afirmação de que as sobrecargas do operador resolvem de maneira semelhante não é. Eles são bem diferentes e é por isso que .Equals resolverá a string enquanto == resolverá o objeto.
MikeKulls
8
Para ser claro, o objecttipo (observe a fonte monoespaçada) é tecnicamente destinado a ser "uma expressão do tipo System.Object". Não tem nada a ver com o tipo de tempo de execução da instância que é referida pela expressão. Eu acho que a afirmação "operadores definidos pelo usuário são tratados como virtualmétodos" é extremamente enganosa. Eles são tratados como métodos sobrecarregados e dependem apenas do tipo de tempo de compilação dos operandos. De fato, após o conjunto de operadores definidos pelo usuário candidatos é calculado, o resto do procedimento de ligação será exatamente o algoritmo de resolução de sobrecarga de método
Mehrdad Afshari
4
@DominicCronin A parte enganosa é que a virtualresolução do método depende do tipo de tempo de execução real de uma instância, enquanto isso é completamente ignorado na resolução de sobrecarga do operador, e esse é realmente o ponto principal da minha resposta.
Mehrdad Afshari
314

Ao comparar uma referência de objeto a uma sequência de caracteres (mesmo que a referência de objeto se refira a uma sequência de caracteres), o comportamento especial do ==operador específico da classe de sequência é ignorado.

Normalmente (quando não estiver lidando com seqüências de caracteres, isto é), Equalscompara valores , enquanto ==compara referências de objetos . Se dois objetos que você está comparando estão se referindo à mesma instância exata de um objeto, ambos retornam true, mas se um tiver o mesmo conteúdo e vier de uma fonte diferente (é uma instância separada com os mesmos dados), apenas Igual será retornar verdadeiro. No entanto, como observado nos comentários, string é um caso especial, porque substitui o ==operador, de modo que, ao lidar exclusivamente com referências de string (e não referências a objetos), apenas os valores são comparados, mesmo que sejam instâncias separadas. O código a seguir ilustra as diferenças sutis nos comportamentos:

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));

A saída é:

True True True
False True True
False False True
BlueMonkMN
fonte
8
Spot on. O operador '==' compara referências de objetos (comparação superficial) enquanto .Equals () compara o conteúdo de objetos (comparação profunda). Como disse @mehrdad, .Equals () é substituído para fornecer essa comparação profunda de conteúdo.
Andrew
1
Deixarei o post aqui, porque acho valioso enfatizar o que não está acontecendo, pois é preciso prestar muita atenção para perceber. (E eu acho que o código para demonstrar os entendimentos corretas e incorretas vale a pena também.) Espero que a classificação não vai abaixo de 0.
BlueMonkMN
5
Certamente String implementa um operador == personalizado. Caso contrário, usar == não compararia o conteúdo. Portanto, String é um mau exemplo para usar aqui, pois não nos ajuda a entender o caso geral em que nenhum operador personalizado foi definido.
Dominic Cronin
6
+1 no exemplo de código épico, que me fez entender isso. Mostra o caso geral do tipo estático (tipo do lado esquerdo) sendo objeto e o caso específico do tipo estático (tipo / RHS) sendo sequência. E toca bem na internação de cordas.
barlop
2
@badsamaritan Por causa da seqüência de internar
Alexander Derck
46

==e .Equalsambos dependem do comportamento definido no tipo real e no tipo real no site da chamada. Ambos são apenas métodos / operadores que podem ser substituídos em qualquer tipo e com qualquer comportamento que o autor desejar. Na minha experiência, acho que é comum as pessoas implementarem .Equalsem um objeto, mas negligenciam a implementação do operador ==. Isso significa que .Equalsrealmente medirá a igualdade dos valores, enquanto ==medirá se eles são ou não a mesma referência.

Quando estou trabalhando com um novo tipo cuja definição está em fluxo ou escrevendo algoritmos genéricos, acho que a melhor prática é a seguinte

  • Se eu quiser comparar referências em C #, uso Object.ReferenceEqualsdiretamente (não é necessário no caso genérico)
  • Se eu quiser comparar valores eu uso EqualityComparer<T>.Default

Em alguns casos, quando sinto que o uso de ==é ambíguo, explicitamente utilizarei Object.Referenceiguais no código para remover a ambiguidade.

Eric Lippert publicou recentemente um post no blog sobre o motivo de haver dois métodos de igualdade no CLR. Vale a pena ler

JaredPar
fonte
Bem, Jared, você viola diretamente o famoso "O melhor código não é nenhum código aqui". Isso é realmente justificado? Por outro lado, posso ver de onde isso decorre e por que seria desejável tornar a semântica explícita. Para este caso, prefiro muito a maneira de o VB lidar com a igualdade de objetos. É curto e inequívoco.
219 Konrad Rudolph
@ Konrad, eu realmente deveria ter dito "quando não estou familiarizado com um tipo, acho que a melhor prática é a seguinte". Sim, o VB tem semântica muito melhor aqui, porque realmente separa a igualdade de valor e referência. O C # combina os dois e ocasionalmente causa erros de ambiguidade.
JaredPar
10
Isso não é inteiramente verdade. == não pode ser substituído, é um método estático. Só pode ser sobrecarregado, o que é uma diferença importante. Portanto, o código que é executado para um operador == é vinculado no tempo de compilação, enquanto o Equals é virtual e encontrado no tempo de execução.
Stefan Steinegger
20

== Operador

  1. Se os operandos forem Tipos de Valor e seus valores forem iguais, ele retornará true ou false.
  2. Se operandos são tipos de referência, com exceção da cadeia de caracteres e ambos se referem à mesma instância (mesmo objeto), ele retorna true else false.
  3. Se operandos forem do tipo string e seus valores forem iguais, retornará true else false.

.É igual a

  1. Se os operandos são Tipos de Referência , eles executam Igualdade de Referência, ou seja, se ambos se referem à mesma instância (mesmo objeto), retornam true ou false.
  2. Se os operandos são tipos de valor , ao contrário do operador ==, ele verifica primeiro o tipo e, se os tipos são iguais, executa o operador ==, caso contrário, retorna false.
kashif
fonte
2
Isso não está correto. O ==operador pode ser sobrecarregado para qualquer tipo, não apenas para string. A descrição de uma exceção de caso especial apenas para cadeia de caracteres representa incorretamente a semântica do operador. Seria mais preciso, embora talvez não seja muito útil, dizer "se os operandos são tipos de referência, eles retornam true se os operandos se referem ao mesmo objeto, a menos que haja uma sobrecarga aplicável; nesse caso, a implementação dessa sobrecarga determina o resultado. " O mesmo acontece Equalscom a complicação adicional de ser um método virtual, para que seu comportamento possa ser substituído e sobrecarregado.
Phoog
19

Em primeiro lugar, não é uma diferença. Para números

> 2 == 2.0
True

> 2.Equals(2.0)
False

E para cordas

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

> x.Equals(null)
NullReferenceException

Nos dois casos, ==comporta-se de maneira mais útil do que.Equals

Coronel Panic
fonte
2
Não sei se consideraria boa a coerção de tipos integrais com tipos de ponto flutuante com o ==operador. Por exemplo, 16777216.0f deve ser igual (int) 16777217, (duplo) 16777217.0, ambos ou nenhum? As comparações entre tipos integrais são boas, mas as comparações de ponto flutuante só devem ser realizadas IMHO com valores explicitamente convertidos em tipos correspondentes. A comparação de a floatcom algo que não seja a float, ou a doublecom algo que não seja a double, me parece um cheiro de código principal que não deve ser compilado sem diagnóstico.
Supercat 16/08
1
@ supercat Eu concordo - é angustiante que x == yisso não implique x/3 == y/3(tente x = 5e y = 5.0).
Coronel Panic
Considero o uso de /para a divisão inteira um defeito no design de C # e Java. Pascal dive até VB.NET ` are much better. The problems with == `são piores, no entanto: x==ye y==znão implica isso x==z(considere os três números no meu comentário anterior). Quanto à relação que você sugere, mesmo se xe ysão ambos floatou ambos double, x.equals((Object)y)não implica que 1.0f/x == 1,0f / y` (se eu tivesse meus druthers, isso garantiria que; mesmo ==que não distinga positivo e zero, Equalsdeveria).
Supercat 23/08
Isso é normal, porque o primeiro parâmetro de Equals () é uma string!
Whiplash
17

Tanto quanto eu entendo, a resposta é simples:

  1. == compara referências de objetos.
  2. .Equals compara o conteúdo do objeto.
  3. String tipos de dados sempre agem como comparação de conteúdo.

Espero estar correto e que tenha respondido sua pergunta.

Liraz Shaka Amir
fonte
15

Eu acrescentaria que, se você converter seu objeto em uma string, ele funcionará corretamente. É por isso que o compilador emitirá um aviso dizendo:

Possível comparação de referência não intencional; para obter uma comparação de valores, gire o lado esquerdo para digitar 'string'

MikeKulls
fonte
1
Exatamente. @DominicCronin: sempre observe os avisos em tempo de compilação. Se você tiver object expr = XXX; if (expr == "Energy") { ... }, como o lado esquerdo é do tipo em tempo de compilação object, o compilador precisa usar a sobrecarga operator ==(object, object). Ele verifica a igualdade de referência. Se isso dará trueou falsepode ser difícil de prever devido à internação de strings . Se você souber que o lado esquerdo é nulldo tipo ou string, gire o lado esquerdo para stringantes de usar ==.
Jeppe Stig Nielsen
para colocar parte disso de outra maneira. == (para determinar se usa igualdade de referência ou igualdade de valor) depende do tipo de tempo de compilação / tipo estático / tipo do lado esquerdo. (esse é o tipo que é resolvido em uma análise de tempo de compilação). Em vez do tipo de tempo de execução / tipo dinâmico / tipo RHS. O código do BlueMonkMN mostra isso, embora não com a transmissão.
barlop
5

Como a versão estática do .Equalmétodo não foi mencionada até agora, gostaria de adicioná-lo aqui para resumir e comparar as três variações.

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

onde MyStringé uma variável que vem de outro lugar no código.

Informações de plano de fundo e para o verão:

Em Java, usar ==para comparar seqüências de caracteres não deve ser usado. Menciono isso no caso de você precisar usar os dois idiomas e também informar que o uso de== também pode ser substituído por algo melhor em C #.

No C #, não há diferença prática para comparar seqüências de caracteres usando o Método 1 ou 2, desde que ambas sejam do tipo seqüência de caracteres. No entanto, se um é nulo, outro de outro tipo (como um número inteiro) ou um objeto que possui uma referência diferente, como mostra a pergunta inicial, você pode experimentar que comparar o conteúdo por igualdade pode não retornar o que você espera.

Solução sugerida:

Como o uso ==não é exatamente o mesmo que o da .Equalscomparação, você pode usar o método estático String.Equals . Dessa forma, se os dois lados não forem do mesmo tipo, você ainda comparará o conteúdo e se um for nulo, evitará a exceção.

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

É um pouco mais para escrever, mas na minha opinião, mais seguro de usar.

Aqui estão algumas informações copiadas da Microsoft:

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

Parâmetros

a Corda

A primeira string a comparar, ou null.

b Corda

A segunda string para comparar, ou null.

Devoluções Boolean

truese o valor de afor o mesmo que o valor de b; caso contrário false,. Se ambos ae bsão null, o método retorna true.

Mario Levesque
fonte
5

Apenas como uma adição às respostas já boas: Esse comportamento NÃO se limita a Strings ou compara diferentes tipos de número. Mesmo se os dois elementos forem do tipo objeto do mesmo tipo subjacente. "==" não funcionará.

A captura de tela a seguir mostra os resultados da comparação de dois objetos {int} - valores

Exemplo do VS2017

Ole Albers
fonte
2

Estou um pouco confuso aqui. Se o tipo de conteúdo do tempo de execução for do tipo string, ambos == e Equals deverão retornar true. No entanto, como esse não parece ser o caso, o tipo de conteúdo em tempo de execução não é uma string e chamar Equals está fazendo uma igualdade referencial e isso explica por que Equals ("Energy Attack") falha. No entanto, no segundo caso, a decisão sobre qual operador == estático sobrecarregado deve ser chamado é tomada em tempo de compilação e essa decisão parece ser == (string, string). isso sugere que o conteúdo fornece uma conversão implícita em string.

Mehmet Aras
fonte
2
Você tem isso de volta à frente. Para começar, Equals ("Energy Attack") não falha, == é o que retorna false. O == falha porque está usando o == from object, não string.
MikeKulls
Por padrão, o operador == testa a igualdade de referência, determinando se duas referências indicam o mesmo objeto. Portanto, os tipos de referência não precisam implementar o operador == para obter essa funcionalidade. Quando um tipo é imutável, ou seja, os dados contidos na instância não podem ser alterados, sobrecarregar o operador == para comparar a igualdade de valor em vez da igualdade de referência pode ser útil porque, como objetos imutáveis, eles podem ser considerados os mesmos desde que como eles têm o mesmo valor. Não é uma boa idéia substituir o operador == em tipos não imutáveis.
Wajeed-MSFT
2

Há uma outra dimensão na resposta anterior de @BlueMonkMN. A dimensão adicional é que a resposta à pergunta do título do @ Drahcir, como é afirmada, também depende de como chegamos ao stringvalor. 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));

A saída é:

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
fonte
2

Adicionando mais um ponto à resposta.

.EqualsTo() O método fornece provisão para comparar com a cultura e diferencia maiúsculas de minúsculas.

Bala
fonte
0

O ==token no C # é usado para dois operadores diferentes de verificação de igualdade. Quando o compilador encontra esse token, ele verifica se um dos tipos comparados implementou uma sobrecarga de operador de igualdade para os tipos de combinação específicos que estão sendo comparados (*) ou para uma combinação de tipos nos quais os dois tipos podem ser convertidos. Se o compilador encontrar uma sobrecarga, ele a utilizará. Caso contrário, se os dois tipos são de referência e não são classes não relacionadas (podem ser uma interface ou podem ser classes relacionadas), o compilador considerará== um operador de comparação de referência. Se nenhuma das condições se aplicar, a compilação falhará.

Observe que alguns outros idiomas usam tokens separados para os dois operadores de verificação de igualdade. No VB.NET, por exemplo, o =token é usado em expressões exclusivamente para o operador de verificação de igualdade sobrecarregável e Isé usado como operador de teste de referência ou teste nulo. Um uso =em um tipo que não substitui o operador de verificação de igualdade falhará, assim como a tentativa de usarIs para qualquer outro propósito que não seja testar a igualdade ou a nulidade de referência.

(*) Os tipos geralmente sobrecarregam a igualdade apenas para comparação com eles mesmos, mas pode ser útil para os tipos sobrecarregar o operador de igualdade para comparação com outros tipos específicos; por exemplo, intpoderia ter (e o IMHO deveria ter, mas não definiu) operadores de igualdade para comparação com float, para que 16777217 não se reportasse igual a 16777216f. Como é, como esse operador não está definido, o C # promoverá o intto float, arredondando-o para 16777216f antes que o operador de verificação de igualdade o veja; esse operador então vê dois números iguais de ponto flutuante e os informa como iguais, sem saber do arredondamento ocorrido.

supercat
fonte
Em vez de ter uma comparação int-float retornar false, eu prefiro a abordagem que o F # usa, que é desaprovar essa comparação. Em seguida, o programador pode decidir se e como lidar com o fato de que os valores têm um tipo diferente. Porque às vezes, afinal, fazer querer tratar 3como sendo igual a 3.0f. Se exigirmos que o programador diga o que se pretende em todos os casos, não há risco de comportamento padrão levar a resultados indesejados, pois não há comportamento padrão.
Phoog
@ Phoho: Meu sentimento pessoal é que as línguas devem ter seus meios "normais" de testar a igualdade, implementar uma relação de equivalência e proibir todas as combinações de operandos para as quais não o faria. Não vejo uma grande vantagem de ter um idioma para verificar a igualdade entre números inteiros e flutuantes, confirmando que um flutuante representa precisamente um número inteiro que corresponde ao int, em vez de simplesmente proibir essas comparações, mas consideraria uma das abordagens superior ao desempenho do idioma uma conversão com perdas antes da comparação.
supercat
0

Respostas e exemplos realmente ótimos!

Gostaria apenas de acrescentar a diferença fundamental entre os dois,

Operadores como ==não polimórficos, enquanto Equalsé

Com esse conceito em mente, se você elaborar algum exemplo (olhando para o tipo de referência à esquerda e à direita e verificando / sabendo se o tipo realmente tem == operador sobrecarregado e igual a superação), você certamente encontrará a resposta certa .

Manish Basantani
fonte
-2

==

O operador == pode ser usado para comparar duas variáveis ​​de qualquer tipo e simplesmente compara os bits .

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

Nota: existem mais zeros no lado esquerdo do int, mas não nos importamos com isso aqui.

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

Lembre-se de que o operador se preocupa apenas com o padrão dos bits na variável.

Use == Se duas referências (primitivas) se referirem ao mesmo objeto no heap.

As regras são as mesmas, independentemente de a variável ser uma referência ou 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 é verdadeiro a == b é falso

o padrão de bits é o mesmo para a e c, portanto são iguais usando ==.

Igual():

Use o método equals () para ver se dois objetos diferentes são iguais .

Como dois objetos String diferentes que representam os caracteres em "Jane"

Sanchit
fonte
2
Isto está incorreto. Considere o seguinte: object a = 3; object b = 3; Console.WriteLine(a == b);. A saída é falsa, mesmo que os padrões de bits dos valores sejam os mesmos. Os tipos dos operandos também são importantes. A razão pela qual "não nos importamos" com o número diferente de zeros no seu exemplo é que, quando chamamos o operador igual, o número de zeros é realmente o mesmo , devido à conversão implícita.
Phoog
-2

A única diferença entre Igual e == está na comparação do tipo de objeto. em outros casos, como tipos de referência e tipos de valor, eles são quase os mesmos (ambos são igualdade de bits ou ambos são igualdade de referência).

objeto: Equals: igualdade bit a bit ==: igualdade de referência

string: (iguais e == são iguais para string, mas se uma das string for alterada para objeto, o resultado da comparação será diferente) Igual a: igualdade de bits ==: igualdade de bits

Veja aqui para mais explicações.

Will Yu
fonte
Object.Equals não necessariamente olha para a igualdade bit a bit. É um método virtual e uma substituição pode fazer o que quiser.
Phoog
sim, você está certo, você pode fazer o que quiser substituí-lo. mas o tópico que estamos falando é a implementação padrão. a implementação padrão de Object.Equals é a igualdade de bits.
Will Yu