Por que (objeto) 0 == (objeto) 0 é diferente de ((objeto) 0) .Equals ((objeto) 0)?


117

Por que as seguintes expressões são diferentes?

[1]  (object)0 == (object)0 //false
[2]  ((object)0).Equals((object)0) // true

Na verdade, eu posso entender totalmente [1] porque provavelmente o runtime do .NET vai para boxo inteiro e começa a comparar as referências. Mas por que [2] é diferente?


36
OK, agora que você entendeu a resposta a esta pergunta, verifique seu entendimento prevendo o resultado de: short myShort = 0; int myInt = 0; Console.WriteLine("{0}{1}{2}", myShort.Equals(myInt), myInt.Equals(myShort), myInt == myShort); Agora compare com a realidade. Sua previsão estava correta? Se não, você pode explicar a discrepância?
Eric Lippert

1
@Star, para leitura recomendada, consulte msdn.microsoft.com/en-us/library/vstudio/… para as sobrecargas disponíveis no método int16também conhecido como shortEquals e, em seguida, consulte msdn.microsoft.com/en-us/library/ms173105.aspx . Não quero estragar o quebra-cabeça de Eric Lippert, mas deve ser muito fácil descobrir depois de ler essas páginas.
Sam Skuce

2
Achei que fosse uma pergunta sobre java; pelo menos antes de ver o 'E' em Equals.
seteropere

4
@seteropere Java é realmente diferente: o autoboxing em objetos de cache do Java, portanto, é ((Integer)0)==((Integer)0)avaliado como verdadeiro.
Jules

1
Você também pode tentar IFormattable x = 0; bool test = (object)x == (object)x;. Nenhum novo boxing é executado quando a estrutura já está em uma caixa.
Jeppe Stig Nielsen

Respostas:


151

O motivo pelo qual as chamadas se comportam de maneira diferente é que elas se vinculam a métodos muito diferentes.

O ==caso será vinculado ao operador de igualdade de referência estática. Existem 2 intvalores em caixas independentes criados, portanto, eles não são a mesma referência.

No segundo caso, você vincula ao método de instância Object.Equals. Este é um método virtual que irá filtrar Int32.Equalse verificar se há um inteiro em caixa. Ambos os valores inteiros são 0, portanto, são iguais


O ==caso não chama Object.ReferenceEquals. Ele simplesmente produz a ceqinstrução IL para realizar uma comparação de referência.
Sam Harwell

8
@ 280Z28 Não é apenas porque o compilador o inline?
markmnl

@ 280Z28 Então? Um caso semelhante é que seu método Boolean.ToString aparentemente contém strings codificadas dentro de sua função, em vez de retornar o Boolean.TrueString e o Boolean.FalseString expostos publicamente. É irrelevante; A questão é: ==faz a mesma coisa que ReferenceEquals(no objeto, de qualquer maneira). É tudo apenas otimização interna no lado da MS para evitar chamadas de funções internas desnecessárias em funções frequentemente utilizadas.
Nyerguds

6
A especificação da linguagem C #, parágrafo 7.10.6, diz: Os operadores de igualdade de tipo de referência predefinidos são: bool operator ==(object x, object y); bool operator !=(object x, object y);Os operadores retornam o resultado da comparação das duas referências para igualdade ou não igualdade. Não é um requisito que o método System.Object.ReferenceEqualsseja usado para determinar o resultado. Para @markmnl: Não, o compilador C # não faz inline, isso é algo que o jitter às vezes faz (mas não neste caso). Portanto, 280Z28 está certo, o ReferenceEqualsmétodo não é realmente usado.
Jeppe Stig Nielsen

@JaredPar: É interessante que a especificação diga isso, já que não é assim que a linguagem realmente se comporta. Dada operadores definidos como acima, e variáveis Cat Whiskers; Dog Fido; IDog Fred;(para interfaces não-relacionados ICate IDoge classes não-relacionados Cat:ICate Dog:IDog), as comparações Whiskers==Fidoe Whiskers==34seria legal (o primeiro só poderia ser verdade se Bigodes e Fido foram null; o segundo nunca poderia ser verdade ) Na verdade, um compilador C # rejeitará ambos. Whiskers==Fred;será proibido se Catestiver lacrado, mas permitido se não estiver.
supercat

26

Quando você converte o valor int 0(ou qualquer outro tipo de valor) para object, o valor é inserido em uma caixa . Cada elenco objectproduz uma caixa diferente (ou seja, uma instância de objeto diferente). O ==operador para o objecttipo executa uma comparação de referência, portanto, ele retorna falso, pois o lado esquerdo e o lado direito não são a mesma instância.

Por outro lado, quando você usa Equals, que é um método virtual, ele usa a implementação do tipo boxed real, ou seja Int32.Equals, que retorna true já que ambos os objetos têm o mesmo valor.


18

O ==operador, sendo estático, não é virtual. Ele executará o código exato que a objectclasse definir (`objeto sendo o tipo de tempo de compilação dos operandos), que fará uma comparação de referência, independentemente do tipo de tempo de execução de qualquer um dos objetos.

O Equalsmétodo é um método de instância virtual. Ele executará o código definido no tipo de tempo de execução real do (primeiro) objeto, não o código da objectclasse. Neste caso, o objeto é um int, então ele fará uma comparação de valores, pois é o que o inttipo define para seu Equalsmétodo.


O ==token realmente representa dois operadores, um dos quais é sobrecarregável e outro não. O comportamento do segundo operador é muito diferente daquele de uma sobrecarga em (objeto, objeto).
supercat

13

O Equals()método é virtual.
Portanto, ele sempre chama a implementação concreta, mesmo quando o callite é convertido para object. intsubstitui Equals()para comparar por valor, para que você obtenha a comparação de valor.


10

== Usar: Object.ReferenceEquals

Object.Equals compara o valor.

O object.ReferenceEqualsmétodo compara referências. Ao alocar um objeto, você recebe uma referência que contém um valor que indica sua localização na memória, além dos dados do objeto no heap de memória.

O object.Equalsmétodo compara o conteúdo dos objetos. Ele primeiro verifica se as referências são iguais, assim como object.ReferenceEquals. Mas então ele chama métodos derivados de Equals para testar ainda mais a igualdade. Veja isso:

   System.Object a = new System.Object();
System.Object b = a;
System.Object.ReferenceEquals(a, b);  //returns true

Embora Object.ReferenceEqualsse comporte como um método que usa o ==operador C # em seus operandos, o operador de operador de igualdade de referência C # (que é representado pelo uso ==de tipos de operando para os quais nenhuma sobrecarga é definida) usa uma instrução especial em vez de chamar ReferenceEquals. Além disso, Object.ReferenceEqualsaceitará operandos que só podem corresponder se ambos forem nulos e aceitará operandos que precisam ser coagidos por tipo Objecte, portanto, não podem corresponder a nada, enquanto a versão de igualdade de referência ==se recusaria a compilar tal uso .
supercat

9

O operador C # usa o token ==para representar dois operadores diferentes: um operador de comparação estaticamente sobrecarregável e um operador de comparação de referência não sobrecarregável. Ao encontrar o ==token, ele primeiro verifica se existe alguma sobrecarga de teste de igualdade que seja aplicável aos tipos de operando. Nesse caso, ele invocará essa sobrecarga. Caso contrário, ele verificará se os tipos são aplicáveis ​​ao operador de comparação de referência. Nesse caso, ele usará esse operador. Se nenhum dos operadores for aplicável aos tipos de operando, a compilação falhará.

O código (Object)0não se limita a upcast um Int32para Object: Int32, como todos os tipos de valor, na verdade representa dois tipos, um dos quais descreve valores e locais de armazenamento (como a de zero literal), mas não deriva de qualquer coisa, e um dos quais descreve heap objetos e deriva Object; como apenas o último tipo pode ser atualizado Object, o compilador deve criar um novo objeto de heap desse último tipo. Cada chamada de (Object)0cria um novo objeto heap, de modo que os dois operandos to ==são objetos diferentes, cada um dos quais, independentemente, encapsula o Int32valor 0.

A classe Objectnão possui sobrecargas utilizáveis ​​definidas para o operador igual. Conseqüentemente, o compilador não poderá usar o operador de teste de igualdade sobrecarregado e voltará a usar o teste de igualdade de referência. Como os dois operandos se ==referem a objetos distintos, ele relatará false. A segunda comparação é bem-sucedida porque pergunta a uma instância do objeto heap Int32se ela é igual à outra. Como essa instância sabe o que significa ser igual a outra instância distinta, ela pode responder true.


Além disso, toda vez que você escreve um literal 0em seu código, presumo que ele cria um objeto int no heap para isso. Não é uma referência única a um valor zero estático global (como como eles fizeram String.Empty para evitar a criação de novos objetos de string vazios apenas para inicializar novas strings). Portanto, tenho quase certeza de que mesmo fazendo um 0.ReferenceEquals(0)retornará falso, uma vez que ambos os 0s são Int32objetos recém-criados .
Nyerguds

1
@Nyerguds, tenho certeza de que tudo o que você disse está incorreto, sobre ints, heap, histórico, estática global, etc. 0.ReferenceEquals(0)falhará porque você está tentando chamar um método em uma constante de tempo de compilação. não há nenhum objeto para pendurá-lo. Um int unboxed é uma estrutura, armazenada na pilha. Mesmo int i = 0; i.ReferenceEquals(...)não vai funcionar. Porque System.Int32NÃO herda de Object.
Andrew Backer

@AndrewBacker, System.Int32é a struct, a structé System.ValueType, que ela mesma herda System.Object. É por isso que existe um ToString()método e um Equalsmétodo paraSystem.Int32
Sebastian

1
No entanto, Nyerguds está errado ao afirmar que um Int32 será criado no heap, o que não é o caso.
Sebastian

@SebastianGodelet, estou meio que ignorando os detalhes internos disso. O próprio System.Int32 implementa esses métodos. GetType () é externo Object, e foi aí que parei de me preocupar com isso há muito tempo. Nunca foi necessário ir mais longe. AFAIK, o CLR, lida com os dois tipos de maneira diferente e especial. Não é apenas herança. No isentanto, é um dos dois tipos de dados. Só não queria que ninguém lesse aquele comentário e se desviasse tanto, incluindo aquela estranheza sobre strings vazias que ignora a internação de strings.
Andrew Backer

3

Ambas as verificações são diferentes. O primeiro verifica a identidade , o segundo verifica a igualdade . Em geral, dois termos são idênticos, se eles se referem ao mesmo objeto. Isso implica que eles são iguais. Dois termos são iguais, se seus valores forem iguais.

Em termos de programação, a identidade geralmente é confundida por igualdade de referência. Se o ponteiro para ambos os termos for igual (!), O objeto para o qual eles estão apontando é exatamente o mesmo. No entanto, se os ponteiros forem diferentes, o valor dos objetos para os quais eles estão apontando ainda pode ser igual. Em C #, a identidade pode ser verificada usando o Object.ReferenceEqualsmembro estático , enquanto a igualdade é verificada usando o Object.Equalsmembro não estático . Como você está convertendo dois inteiros para objetos (o que é chamado de "boxing", aliás), o operador ==de objectrealiza a primeira verificação, que por padrão é mapeada Object.ReferenceEqualse verifica a identidade. Se você chamar explicitamente o não-estático Equals-member, o despacho dinâmico resultará em uma chamada para Int32.Equals, que verifica a igualdade.

Ambos os conceitos são semelhantes, mas não iguais. Eles podem parecer confusos no início, mas a pequena diferença é muito importante! Imagine duas pessoas, nomeadamente "Alice" e "Bob". Os dois estão morando em uma casa amarela. Partindo do pressuposto de que Alice e Bob estão morando em um bairro onde as casas têm apenas cores diferentes, os dois poderiam morar em casas amarelas diferentes. Se você comparar as duas casas, você reconhecerá que são absolutamente iguais, porque ambas são amarelas! No entanto, eles não estão compartilhando a mesma casa e, portanto, suas casas são iguais , mas não idênticas . A identidade implicaria que eles estão morando na mesma casa.

Nota : alguns idiomas estão definindo o ===operador para verificar a identidade.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.