Pourquoi (objet) 0 == (objet) 0 est-il différent de ((objet) 0) .Equals ((objet) 0)?

117

Pourquoi les expressions suivantes sont-elles différentes?

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

En fait, je peux totalement comprendre [1] parce que probablement le runtime .NET sera boxl'entier et commencera à comparer les références à la place. Mais pourquoi est [2] différent?

André Pena
la source
36
OK, maintenant que vous comprenez la réponse à cette question, vérifiez votre compréhension en prédisant le résultat de: short myShort = 0; int myInt = 0; Console.WriteLine("{0}{1}{2}", myShort.Equals(myInt), myInt.Equals(myShort), myInt == myShort); Maintenant, comparez-le à la réalité. Votre prédiction était-elle correcte? Sinon, pouvez-vous expliquer l'écart?
Eric Lippert
1
@Star, pour la lecture recommandée, voir msdn.microsoft.com/en-us/library/vstudio/… pour les surcharges disponibles sur la méthode int16aka shortEquals, puis regardez msdn.microsoft.com/en-us/library/ms173105.aspx . Je ne veux pas gâcher le puzzle d'Eric Lippert, mais il devrait être assez facile à comprendre une fois que vous aurez lu ces pages.
Sam Skuce
2
Je pensais que c'était une question java; au moins avant de voir le «E» dans Equals.
seteropere
4
@seteropere Java est en fait différent: l'autoboxing dans Java met en cache les objets, donc prend la valeur ((Integer)0)==((Integer)0)true.
Jules
1
Vous pouvez également essayer IFormattable x = 0; bool test = (object)x == (object)x;. Aucune nouvelle boxe n'est effectuée lorsque la structure est déjà dans une boîte.
Jeppe Stig Nielsen

Réponses:

151

La raison pour laquelle les appels se comportent différemment est qu'ils se lient à des méthodes très différentes.

Le ==cas sera lié à l'opérateur d'égalité de référence statique. Il y a 2 box indépendantsint valeurs créées, elles ne sont donc pas la même référence.

Dans le second cas, vous vous liez à la méthode d'instance Object.Equals. C'est une méthode virtuelle qui filtrera jusqu'à Int32.Equalset qui vérifie un entier encadré. Les deux valeurs entières sont 0 et sont donc égales

JaredPar
la source
L' ==affaire n'appelle pas Object.ReferenceEquals. Il produit simplement l' ceqinstruction IL pour effectuer une comparaison de référence.
Sam Harwell
8
@ 280Z28 n'est-ce pas seulement parce que le compilateur l'a intégré?
markmnl
@ 280Z28 Alors? Un cas similaire est que leur méthode Boolean.ToString contient apparemment des chaînes codées en dur dans sa fonction, plutôt que de renvoyer les Boolean.TrueString et Boolean.FalseString exposés publiquement. Ce n'est pas pertinent; Le fait est, ==fait la même chose que ReferenceEquals(sur Object, de toute façon). C'est juste une optimisation interne du côté de MS pour éviter les appels de fonctions internes inutiles sur des fonctions souvent utilisées.
Nyerguds
6
La spécification du langage C #, paragraphe 7.10.6, dit: Les opérateurs d'égalité de type référence prédéfinis sont: bool operator ==(object x, object y); bool operator !=(object x, object y);Les opérateurs retournent le résultat de la comparaison des deux références pour l'égalité ou la non-égalité. Il n'est pas obligatoire que la méthode System.Object.ReferenceEqualssoit utilisée pour déterminer le résultat. À @markmnl: Non, le compilateur C # n'est pas en ligne, c'est quelque chose que la gigue fait parfois (mais pas dans ce cas). Donc 280Z28 a raison, la ReferenceEqualsméthode n'est pas réellement utilisée.
Jeppe Stig Nielsen
@JaredPar: C'est intéressant que la spécification le dise, car ce n'est pas ainsi que le langage se comporte réellement. Étant donné les opérateurs définis comme ci - dessus, et les variables Cat Whiskers; Dog Fido; IDog Fred;(pour les interfaces non apparentées ICatet IDoget les classes non liées Cat:ICatet Dog:IDog), les comparaisons Whiskers==Fidoet Whiskers==34serait légal (la première ne pouvait être vrai si Moustaches et Fido étaient tous deux nuls, le second ne pourrait jamais être vrai ). En fait, un compilateur C # rejettera les deux. Whiskers==Fred;sera interdit s'il Catest scellé, mais autorisé s'il ne l'est pas.
supercat
26

Lorsque vous transtypez la valeur int 0(ou tout autre type de valeur) en object, la valeur est encadrée . Chaque objectconversion en produit une boîte différente (c'est-à-dire une instance d'objet différente). L' ==opérateur duobject type effectue une comparaison de référence, il renvoie donc false car le côté gauche et le côté droit ne sont pas la même instance.

D'autre part, lorsque vous utilisez Equals, qui est une méthode virtuelle, il utilise l'implémentation du type boxed réel, c'est Int32.Equals-à- dire , qui renvoie true puisque les deux objets ont la même valeur.

Thomas Levesque
la source
18

L' ==opérateur, étant statique, n'est pas virtuel. Il exécutera le code exact que leobject défini par classe (`objet étant le type à la compilation des opérandes), qui effectuera une comparaison de référence, quel que soit le type d'exécution de l'un ou l'autre objet.

La Equalsméthode est une méthode d'instance virtuelle. Il exécutera le code défini dans le type d'exécution réel du (premier) objet, pas le code de la objectclasse. Dans ce cas, l'objet est un int, donc il effectuera une comparaison de valeurs, car c'est ce que le inttype définit pour sa Equalsméthode.

Servy
la source
Le ==jeton représente en fait deux opérateurs, dont l'un est surchargeable et l'autre non. Le comportement du deuxième opérateur est très différent de celui d'une surcharge sur (objet, objet).
supercat du
13

La Equals()méthode est virtuelle.
Par conséquent, il appelle toujours l'implémentation concrète, même lorsque le site d'appel est converti object. intremplace Equals()pour comparer par valeur, vous obtenez donc une comparaison de valeur.

SLaks
la source
10

== Utilisation: Object.ReferenceEquals

Object.Equals compare la valeur.

le object.ReferenceEquals méthode compare les références. Lorsque vous allouez un objet, vous recevez une référence contenant une valeur indiquant son emplacement mémoire en plus des données de l'objet sur le tas de mémoire.

La object.Equalsméthode compare le contenu des objets. Il vérifie d'abord si les références sont égales, tout comme object.ReferenceEquals. Mais ensuite, il fait appel aux méthodes Equals dérivées pour tester davantage l'égalité. Regarde ça:

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

la source
Bien qu'il Object.ReferenceEqualsse comporte comme une méthode qui utilise l' ==opérateur C # sur ses opérandes, l'opérateur d'opérateur d'égalité de référence C # (qui est représenté en utilisant des ==types d'opérandes pour lesquels aucune surcharge n'est définie) utilise une instruction spéciale plutôt que d'appeler ReferenceEquals. De plus, Object.ReferenceEqualsacceptera les opérandes qui ne pourraient correspondre que si les deux sont nuls, et acceptera les opérandes qui doivent être forcés de type Objectet ne peuvent donc pas correspondre à quoi que ce soit, tandis que la version d'égalité de référence de ==refuserait de compiler une telle utilisation .
supercat
9

L'opérateur C # utilise le jeton ==pour représenter deux opérateurs différents: un opérateur de comparaison statiquement surchargeable et un opérateur de comparaison de référence non surchargeable. Lorsqu'il rencontre le ==jeton, il vérifie d'abord s'il existe une surcharge de test d'égalité applicable aux types d'opérande. Si tel est le cas, il invoquera cette surcharge. Sinon, il vérifiera si les types sont applicables à l'opérateur de comparaison de références. Si tel est le cas, il utilisera cet opérateur. Si aucun opérateur n'est applicable aux types d'opérande, la compilation échouera.

Le code (Object)0ne se contente pas upcast un Int32à Object: Int32, comme tous les types de valeur, représente en fait deux types, l' un qui décrit les valeurs et les lieux de stockage (tels que le zéro littéral), mais ne tire pas de quoi que ce soit, et l' un qui décrit tas d'objets et dérive de Object; car seul ce dernier type peut être converti Object, le compilateur doit créer un nouvel objet de tas de ce dernier type. Chaque appel de (Object)0crée un nouvel objet de tas, de sorte que les deux opérandes à ==sont des objets différents, chacun desquels, indépendamment, encapsule la Int32valeur 0.

La classe Objectn'a aucune surcharge utilisable définie pour l'opérateur égal. Par conséquent, le compilateur ne pourra pas utiliser l'opérateur de test d'égalité surchargé et reviendra à l'utilisation du test d'égalité de référence. Parce que les deux opérandes ==font référence à des objets distincts, il sera signalé false. La deuxième comparaison réussit car elle demande à une instance d'objet de tas Int32si elle est égale à l'autre. Parce que cette instance sait ce que signifie être égale à une autre instance distincte, elle peut répondre true.

supercat
la source
De plus, chaque fois que vous écrivez un littéral 0dans votre code, je suppose que cela crée un objet int dans le tas pour cela. Ce n'est pas une référence unique à une valeur statique zéro globale (comme la façon dont ils ont fait String.Empty pour éviter de faire une nouvelle chaîne vide seulement des objets pour initialiser de nouvelles chaînes) Je suis assez sûr que cela même 0.ReferenceEquals(0)retournera faux, puisque les deux sont 0s Int32objets nouvellement créés .
Nyerguds
1
@Nyerguds, je suis presque sûr que tout ce que vous avez dit est incorrect, à propos des entiers, du tas, de l'histoire, de la statique globale, etc. 0.ReferenceEquals(0)échouera parce que vous essayez d'appeler une méthode sur une constante de temps de compilation. il n'y a aucun objet pour le suspendre. Un int unboxed est une structure, stockée sur la pile. Même int i = 0; i.ReferenceEquals(...)ne fonctionnera pas. Parce System.Int32que n'hérite PAS de Object.
Andrew Backer
@AndrewBacker, System.Int32est un struct, un structest System.ValueType, qui hérite lui-même System.Object. C'est pourquoi il existe une ToString()méthode et une Equalsméthode pourSystem.Int32
Sebastian
1
Néanmoins, Nyerguds a tort de déclarer qu'un Int32 sera créé sur le tas, ce qui n'est pas le cas.
Sebastian
@SebastianGodelet, j'ignore en quelque sorte les éléments internes. System.Int32 implémente lui-même ces méthodes. GetType () est externe surObject , et c'est là que j'ai arrêté de m'inquiéter à ce sujet il y a longtemps. Il n'a jamais été nécessaire d'aller plus loin. AFAIK le CLR gère les deux types différemment, et spécialement. Ce n'est pas seulement un héritage. isCependant, c'est l' un des deux types de données. Je ne voulais tout simplement pas que quiconque lise ce commentaire et s'écarte si loin de la piste, y compris cette étrangeté à propos des chaînes vides qui ignore l'internement de chaînes.
Andrew Backer
3

Les deux contrôles sont différents. Le premier vérifie l' identité , le second l' égalité . En général, deux termes sont identiques s'ils font référence au même objet. Cela implique qu'ils sont égaux. Deux termes sont égaux, si leurs valeurs sont identiques.

En termes de programmation, l'identité est généralement définie par l'égalité de référence. Si le pointeur vers les deux termes est égal (!), L'objet sur lequel il pointe est exactement le même. Cependant, si les pointeurs sont différents, la valeur des objets sur lesquels ils pointent peut toujours être égale. En C #, l'identité peut être vérifiée à l'aide du Object.ReferenceEqualsmembre statique , tandis que l'égalité est vérifiée à l'aide du Object.Equalsmembre non statique . Puisque vous transtypez deux entiers en objets (ce qui est appelé "boxing", btw), l'opérateur ==de objecteffectue la première vérification, qui est par défaut mappée Object.ReferenceEqualset vérifie l'identité. Si vous appelez explicitement le membre non statique Equals, la répartition dynamique entraîne un appel à Int32.Equals, qui vérifie l'égalité.

Les deux concepts sont similaires, mais pas identiques. Ils peuvent sembler déroutants pour le premier, mais la petite différence est très importante! Imaginez deux personnes, à savoir «Alice» et «Bob». Ils vivent tous les deux dans une maison jaune. Sur la base de l'hypothèse, qu'Alice et Bob vivent dans un quartier, où les maisons ne diffèrent que par leur couleur, ils pourraient tous deux vivre dans des maisons jaunes différentes. Si vous comparez les deux maisons, vous reconnaîtrez qu'elles sont absolument identiques, car elles sont toutes les deux jaunes! Cependant, ils ne partagent pas la même maison et donc leurs maisons sont égales , mais pas identiques . L'identité impliquerait qu'ils vivent dans la même maison.

Remarque : certaines langues définissent l' ===opérateur pour vérifier l'identité.

Carsten
la source