Qual é a diferença entre == e Equals () para primitivas em C #?

180

Considere este código:

int age = 25;
short newAge = 25;
Console.WriteLine(age == newAge);  //true
Console.WriteLine(newAge.Equals(age)); //false
Console.ReadLine();

Ambos inte shortsão tipos primitivos, mas uma comparação com ==retornos verdadeiro e uma comparação com Equalsretornos falsos.

Por quê?

Mohammad Zargarani
fonte
9
@OrangeDog Por favor, pense sobre a questão e vote para fechar
4
Está faltando a tentativa reversa óbvia:Console.WriteLine(age.Equals(newAge));
ANeves
3
A duplicata não explica esse comportamento; é apenas sobre o que Equals()é em geral.
SLaks 22/01
37
Respondi a essa pergunta exata no blog Coverity há alguns dias. blog.coverity.com/2014/01/13/inconsistent-equality
Eric Lippert
5
@CodesInChaos: a especificação realmente usa o termo "tipos primitivos" duas vezes sem defini-lo; a implicação é que os tipos primitivos são tipos de valor internos, mas isso nunca é esclarecido. Eu recomendei a Mads que o termo simplesmente seja retirado da especificação, pois parece criar mais confusão do que remove.
precisa saber é o seguinte

Respostas:

262

Resposta curta:

A igualdade é complicada.

Resposta detalhada:

Os tipos de primitivas substituem a base object.Equals(object)e retornam true se a caixa objectfor do mesmo tipo e valor. (Observe que ele também funcionará para tipos anuláveis; os tipos anuláveis ​​não nulos sempre se encaixam em uma instância do tipo subjacente.)

Como newAgeé a short, seu Equals(object)método só retornará true se você passar um short in a box com o mesmo valor. Você está passando um in a box int, então ele retorna false.

Por outro lado, o ==operador é definido como tendo dois ints (ou shorts ou longs).
Quando você o chama com an inte a short, o compilador converte implicitamente o shortem inte compara os ints resultantes pelo valor.

Outras maneiras de fazê-lo funcionar

Os tipos primitivos também têm seu próprio Equals()método que aceita o mesmo tipo.
Se você escrever age.Equals(newAge), o compilador selecionará int.Equals(int)a melhor sobrecarga e converterá implicitamente shortem int. Ele retornará true, pois esse método simplesmente compara os ints diretamente.

shorttambém possui um short.Equals(short)método, mas intnão pode ser convertido implicitamente em short, portanto você não está chamando.

Você pode forçá-lo a chamar esse método com uma conversão:

Console.WriteLine(newAge.Equals((short)age)); // true

Isso chamará short.Equals(short)diretamente, sem boxe. Se agefor maior que 32767, lançará uma exceção de estouro.

Você também pode chamar a short.Equals(object)sobrecarga, mas passar explicitamente um objeto em caixa para que ele obtenha o mesmo tipo:

Console.WriteLine(newAge.Equals((object)(short)age)); // true

Como a alternativa anterior, isso provocará um estouro se não couber em a short. Ao contrário da solução anterior, ele encaixotará o shortobjeto em um objeto, desperdiçando tempo e memória.

Código fonte:

Aqui estão os dois Equals()métodos do código-fonte real:

    public override bool Equals(Object obj) {
        if (!(obj is Int16)) {
            return false;
        }
        return m_value == ((Int16)obj).m_value;
    }

    public bool Equals(Int16 obj)
    {
        return m_value == obj;
    }

Leitura adicional:

Veja Eric Lippert .

SLaks
fonte
3
@ Slaks, se chamarmos long == int, intimplicitamente convertido para a longdireita?
Selman Genç 22/01
1
E sim, escrevi tudo isso sem realmente tentar.
SLaks
1
Lembre-se de que, no código da pergunta, se alguém mudar int age = 25;para const int age = 25;, o resultado será alterado. Isso ocorre porque uma conversão implícita de intpara shortexiste nesse caso. Consulte Conversões implícitas de expressão constante .
Jeppe Stig Nielsen
2
@SLaks sim, mas o texto da sua resposta "o valor passado" pode ser interpretado de duas maneiras (como o valor passado pelo desenvolvedor ou o valor que é realmente passado pelo CLR após o unboxing). Eu estou supondo que o usuário casual, que já não sabe as respostas aqui vai lê-lo como o ex
JaredPar
2
@ Rachel: Exceto que isso não é verdade; o operador padrão == compara tipos de referência por referência. Para tipos de valor e tipos que sobrecarregam ==, isso não ocorre.
SLaks
55

Porque não há sobrecarga para short.Equalsque aceita um int. Portanto, isso é chamado:

public override bool Equals(object obj)
{
    return obj is short && this == (short)obj;
}

objnão é um short.. portanto, é falso.

Simon Whitehead
fonte
12

Quando você passa intpara shortigual a, você passa object:

insira a descrição da imagem aqui Portanto, esse pseudocódigo é executado:

return obj is short && this == (short)obj;
Majid
fonte
10

==é usado para verificar uma condição igual, pode ser considerado um operador (operador booleano), apenas para comparar duas coisas e aqui o tipo de dados não importa, pois haveria uma conversão de tipo feita e Equalstambém é usado para verificar condições iguais , mas nesse caso os tipos de dados devem ser os mesmos. N Equals é um método, não um operador.

Abaixo está um pequeno exemplo do que você forneceu e isso esclarecerá a diferença em breve.

int x=1;
short y=1;
x==y;//true
y.Equals(x);//false

no exemplo acima, X e Y têm os mesmos valores, ou seja, 1 e, quando usamos ==, ele retornará true, como no caso de ==, o tipo curto é convertido em int pelo compilador e o resultado é fornecido.

e quando usamos Equals, a comparação é feita, mas a conversão de tipo não é feita pelo compilador, então falso é retornado.

Gente, por favor me avise se eu estiver errado.

user2423959
fonte
6

Em muitos contextos em que um método ou argumento de operador não é do tipo necessário, o compilador C # tentará executar uma conversão implícita de tipo. Se o compilador puder fazer com que todos os argumentos satisfaçam seus operadores e métodos adicionando conversões implícitas, ele o fará sem reclamar, embora em alguns casos (especialmente com testes de igualdade!) Os resultados possam ser surpreendentes.

Além disso, cada tipo de valor como intou shortrealmente descreve um tipo de valor e um tipo de objeto (*). Existem conversões implícitas para converter valores em outros tipos de valores e converter qualquer tipo de valor em seu tipo de objeto correspondente, mas os diferentes tipos de objetos não são implicitamente conversíveis entre si.

Se alguém usa o ==operador para comparar um shorte um int, o shortserá convertido implicitamente a um int. Se o seu valor numérico for igual ao valor de int, o valor intpara o qual foi convertido será igual ao valor intde comparação. Se alguém tentar usar o Equalsmétodo no short para compará-lo com um int, no entanto, a única conversão implícita que satisfaria uma sobrecarga do Equalsmétodo seria a conversão para o tipo de objeto correspondente a int. Quando shortperguntado se ele corresponde ao objeto transmitido, ele observará que o objeto em questão é um inte não um shorte, portanto, concluirá que ele não pode ser igual.

Em geral, embora o compilador não se queixe, deve-se evitar comparar coisas que não são do mesmo tipo; se alguém estiver interessado em saber se a conversão de coisas em uma forma comum daria o mesmo resultado, deve-se realizar essa conversão explicitamente. Considere, por exemplo,

int i = 16777217;
float f = 16777216.0f;

Console.WriteLine("{0}", i==f);

Existem três maneiras pelas quais se pode comparar um inta um float. Alguém pode querer saber:

  1. O floatvalor mais próximo possível da intcorrespondência é float?
  2. O número inteiro faz parte da floatcorrespondência int?
  3. Faça o inte floatrepresente o mesmo valor numérico.

Se alguém tentar comparar um inte floatdiretamente, o código compilado responderá à primeira pergunta; se é isso que o programador pretendia, no entanto, estará longe de ser óbvio. Alterar a comparação para (float)i == fdeixaria claro que o primeiro significado era intencional ou (double)i == (double)ffaria com que o código respondesse à terceira pergunta (e deixasse claro que era o que pretendia).

(*) Mesmo que a especificação C # considere um valor do tipo, por exemplo, System.Int32como um objeto do tipo System.Int32, essa visão é contrariada pelo requisito de que um código seja executado em uma plataforma cuja especificação considere valores e objetos como habitando universos diferentes. Além disso, se Té um tipo de referência e xé a T, uma referência do tipo Tdeve poder fazer referência x. Assim, se uma variável vdo tipo Int32contiver um Object, uma referência do tipo Objectdeve poder manter uma referência vou seu conteúdo. De fato, uma referência do tipo Objectseria capaz de apontar para um objeto contendo dados copiados v, mas não para vsi próprio nem para seu conteúdo. Isso sugeriria que nemvnem seu conteúdo é realmente um Object.

supercat
fonte
1
the only implicit conversion which would satisfy an overload of the Equals method would be the conversion to the object type corresponding to intErrado. Ao contrário do Java, o C # não possui tipos primitivos e em caixa separados. Ele está sendo encaixotado objectporque é a única outra sobrecarga de Equals().
SLaks
A primeira e a terceira questão são idênticas; o valor exato já foi perdido na conversão para float. A conversão de a floatpara a doublenão criará magicamente nova precisão.
SLaks
@ Slaks: de acordo com a especificação ECMA, que descreve a máquina virtual na qual o C # é executado, cada definição de tipo de valor cria dois tipos distintos. A especificação do C # pode dizer que o conteúdo de um local de armazenamento do tipo List<String>.Enumeratore um objeto de pilha do tipo List<String>.Enumeratoré o mesmo, mas a especificação do ECMA / CLI diz que são diferentes e, mesmo quando usados ​​no C #, se comportam de maneira diferente.
Supercat
@SLaks: se ie fcada um deles foi convertido doubleantes da comparação, eles renderiam 16777217.0 e 16777216.0, que são comparados como desiguais. A conversão i floatrenderia 16777216.0f, comparando igual a f.
Supercat
@ Slaks: Para um exemplo simples da diferença entre tipos de local de armazenamento e tipos de objetos em caixa, considere o método bool SelfSame<T>(T p) { return Object.ReferenceEquals((Object)p,(Object)p);}. O tipo de objeto em caixa correspondente a um tipo de valor pode satisfazer o tipo de parâmetro ReferenceEqualsvia uma upcast de preservação de identidade ; o tipo de local de armazenamento, no entanto, requer uma conversão sem preservação de identidade . Se converter um Tpara Uproduz uma referência a algo diferente do original T, isso sugere que a Tnão é realmente um U.
Supercat
5

Equals () é um método da sintaxe da classe System.Object
: bool virtual virtual Equals ()
Recomendação se quisermos comparar o estado de dois objetos, devemos usar o método Equals ()

como indicado acima respostas == operadores comparam os valores são os mesmos.

Por favor, não se confunda com o ReferenceEqual

Referência Equals ()
Sintaxe: public static bool ReferenceEquals ()
Determina se a instância de objetos especificada é da mesma instância

Sugat Mankar
fonte
8
Isso não responde à pergunta.
SLaks
Slaks que eu não expliquei com exemplos, isso é básico da pergunta acima.
Sugat Mankar 23/01
4

O que você precisa entender é que fazer ==sempre acabará chamando um método. A questão é se ligar ==e Equalsacaba ligando / fazendo as mesmas coisas.

Com os tipos de referência, ==sempre verificará primeiro se as referências são iguais ( Object.ReferenceEquals). Equalspor outro lado, pode ser substituído e pode verificar se alguns valores são iguais.

EDIT: para responder svick e adicionar comentário SLaks, aqui está um código IL

int i1 = 0x22; // ldc.i4.s ie pushes an int32 on the stack
int i2 = 0x33; // ldc.i4.s 
short s1 = 0x11; // ldc.i4.s (same as for int32)
short s2 = 0x22; // ldc.i4.s 

s1 == i1 // ceq
i1 == s1 // ceq
i1 == i2 // ceq
s1 == s2 // ceq
// no difference between int and short for those 4 cases,
// anyway the shorts are pushed as integers.

i1.Equals(i2) // calls System.Int32.Equals
s1.Equals(s2) // calls System.Int16.Equals
i1.Equals(s1) // calls System.Int32.Equals: s1 is considered as an integer
// - again it was pushed as such on the stack)
s1.Equals(i1) // boxes the int32 then calls System.Int16.Equals
// - int16 has 2 Equals methods: one for in16 and one for Object.
// Casting an int32 into an int16 is not safe, so the Object overload
// must be used instead.
user276648
fonte
Então, qual método compara dois ints com == chama? Dica: não existe um operator ==método para Int32, mas existe um paraString .
svick
2
Isso não responde à pergunta.
SLaks
@ Slaks: na verdade, não responde a pergunta específica sobre int e breve comparação, você já respondeu. Ainda acho interessante explicar que ==isso não apenas faz mágica, mas simplesmente chama um método (a maioria dos programadores provavelmente nunca implementou / substituiu qualquer operador). Talvez eu pudesse ter adicionado um comentário à sua pergunta em vez de adicionar minha própria resposta. Sinta-se à vontade para atualizar o seu se achar que o que eu disse é relevante.
user276648
Observe que ==nos tipos primitivos não é um operador sobrecarregado, mas um recurso de linguagem intrínseca que é compilado com a ceqinstrução IL.
SLaks
3

== No Primitivo

Console.WriteLine(age == newAge);          // true

Na comparação primitiva, o operador == se comporta bastante óbvio; em C # existem muitos == sobrecarga do operador disponível.

  • string == string
  • int == int
  • uint == uint
  • long == long
  • muito mais

Portanto, neste caso, não há conversão implícita de intpara shortmas shortpara inté possível. Portanto, newAge é convertido em int e ocorre uma comparação que retorna true, pois ambos mantêm o mesmo valor. Portanto, é equivalente a:

Console.WriteLine(age == (int)newAge);          // true

.Equals () em Primitivo

Console.WriteLine(newAge.Equals(age));         //false

Aqui precisamos ver o que é o método Equals (), chamando Equals com uma variável de tipo curto. Portanto, existem três possibilidades:

  • Equals (objeto, objeto) // método estático do objeto
  • Equals (object) // método virtual do objeto
  • Equals (short) // Implementa IEquatable.Equals (short)

O primeiro tipo não é o caso aqui, pois o número de argumentos é diferente, chamando com apenas um argumento do tipo int. O terceiro também é eliminado, como mencionado acima, a conversão implícita de int para short não é possível. Então aqui o segundo tipo de Equals(object)é chamado. O short.Equals(object)é:

bool Equals(object z)
{
  return z is short && (short)z == this;
}

Portanto, aqui a condição foi testada, z is shortque é falsa, pois z é um int e, portanto, retorna falso.

Aqui está um artigo detalhado de Eric Lippert

Zaheer Ahmed
fonte