Quelle est la différence entre == et Equals () pour les primitives en C #?

180

Considérez ce code:

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

Les deux intet shortsont des types primitifs, mais une comparaison avec ==renvoie true et une comparaison avec Equalsrenvoie false.

Pourquoi?

Mohammad Zargarani
la source
9
@OrangeDog Veuillez réfléchir à la question, puis voter pour la fermer
4
Il manque la tentative inverse évidente:Console.WriteLine(age.Equals(newAge));
ANeves
3
Le duplicata n'explique pas ce comportement; c'est à peu près ce qui Equals()est en général.
SLaks
37
J'ai répondu à cette question exacte sur le blog Coverity il y a quelques jours. blog.coverity.com/2014/01/13/inconsistent-equality
Eric Lippert
5
@CodesInChaos: La spécification utilise en fait le terme "types primitifs" deux fois sans jamais le définir; l'implication est que les types primitifs sont des types valeur intégrés, mais cela n'est jamais précisé. J'ai recommandé à Mads que le terme soit simplement supprimé de la spécification car il semble créer plus de confusion qu'il n'en supprime.
Eric Lippert

Réponses:

262

Réponse courte:

L'égalité est compliquée.

Réponse détaillée:

Les types primitifs remplacent la base object.Equals(object)et renvoient true si le boxed objectest du même type et de la même valeur. (Notez que cela fonctionnera également pour les types Nullable; les types Nullable non Null encadrent toujours une instance du type sous-jacent.)

Puisque newAgeest a short, sa Equals(object)méthode ne renvoie vrai que si vous passez un short boxed avec la même valeur. Vous passez une boîte int, donc il retourne faux.

En revanche, l' ==opérateur est défini comme prenant deux ints (ou shorts ou longs).
Lorsque vous l'appelez avec un intet un short, le compilateur convertira implicitement le shortto intet comparera le ints résultant par valeur.

Autres moyens de le faire fonctionner

Les types primitifs ont également leur propre Equals()méthode qui accepte le même type.
Si vous écrivez age.Equals(newAge), le compilateur sélectionnera int.Equals(int)la meilleure surcharge et la convertira implicitement shorten int. Il reviendra alors true, puisque cette méthode compare simplement les ints directement.

shorta également une short.Equals(short)méthode, mais intne peut pas être convertie implicitement en short, donc vous ne l'appelez pas.

Vous pouvez le forcer à appeler cette méthode avec un cast:

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

Cela appellera short.Equals(short)directement, sans boxe. Si ageest supérieur à 32767, il lèvera une exception de dépassement de capacité.

Vous pouvez également appeler la short.Equals(object)surcharge, mais passer explicitement un objet encadré afin qu'il obtienne le même type:

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

Comme l'alternative précédente, cela provoquera un débordement s'il ne rentre pas dans un fichier short. Contrairement à la solution précédente, il enfermera le shortdans un objet, perdant du temps et de la mémoire.

Code source:

Voici les deux Equals()méthodes du code source réel:

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

Lectures complémentaires:

Voir Eric Lippert .

SLaks
la source
3
@SLaks, si nous appelons long == int, intimplicitement converti en longdroite?
Selman Genç
1
Et oui, j'ai écrit tout ça sans vraiment l'essayer.
SLaks
1
N'oubliez pas que, dans le code de la question, si l'on change int age = 25;en const int age = 25;, le résultat changera. C'est parce qu'une conversion implicite de intà shortexiste dans ce cas. Voir Conversions d'expressions constantes implicites .
Jeppe Stig Nielsen
2
@SLaks oui mais le libellé de votre réponse "la valeur passée" peut être interprété dans les deux sens (comme la valeur transmise par le développeur, ou la valeur qui est effectivement transmise par le CLR après unboxing). Je suppose que l'utilisateur occasionnel qui ne connaît pas déjà les réponses ici le lira comme l'ancien
JaredPar
2
@Rachel: Sauf que ce n'est pas vrai; l' opérateur par défaut == compare les types de référence par référence. Pour les types valeur et pour les types surchargés ==, ce n'est pas le cas.
SLaks
55

Parce qu'il n'y a pas de surcharge pour short.Equalscela accepte un int. Par conséquent, cela s'appelle:

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

objn'est pas un short.. donc, c'est faux.

Simon Whitehead
la source
12

Lorsque vous passez intà short's Equals, vous passez object:

entrez la description de l'image ici Donc, ce pseudocode s'exécute:

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

==est utilisé pour vérifier une condition d'égalité, il peut être considéré comme un opérateur (opérateur booléen), juste pour comparer 2 choses et ici le type de données n'a pas d'importance car il y aurait un casting de type fait et Equalsest également utilisé pour vérifier la condition d'égalité , mais dans ce cas, les types de données doivent être identiques. N Equals est une méthode et non un opérateur.

Voici un petit exemple tiré de celui que vous avez fourni et cela clarifiera la différence en bref.

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

dans l'exemple ci-dessus, X et Y ont les mêmes valeurs, c'est-à-dire 1, et lorsque nous l'utilisons ==, il retournera true, comme dans le cas de ==, le type court est converti en int par le compilateur et le résultat est donné.

et quand nous utilisons Equals, la comparaison est faite, mais le cast de type n'est pas fait par le compilateur, donc false est retourné.

Les gars, faites-moi savoir si je me trompe.

user2423959
la source
6

Dans de nombreux contextes où une méthode ou un argument d'opérateur n'est pas du type requis, le compilateur C # tentera d'effectuer une conversion de type implicite. Si le compilateur peut faire en sorte que tous les arguments satisfassent leurs opérateurs et méthodes en ajoutant des conversions implicites, il le fera sans se plaindre, même si dans certains cas (surtout avec des tests d'égalité!) Les résultats peuvent être surprenants.

En outre, chaque type de valeur tel que intou shortdécrit en fait à la fois un type de valeur et un type d'objet (*). Des conversions implicites existent pour convertir des valeurs en d'autres types de valeurs et pour convertir tout type de valeur en son type d'objet correspondant, mais les différents types d'objets ne sont pas implicitement convertibles les uns aux autres.

Si l'on utilise l' ==opérateur pour comparer a shortet an int, le shortsera implicitement converti en un int. Si sa valeur numérique était égale à celle du int, le intauquel il a été converti sera égal intau auquel il est comparé. Si l'on tente d'utiliser la Equalsméthode sur le short pour la comparer à un int, cependant, la seule conversion implicite qui satisferait une surcharge de la Equalsméthode serait la conversion vers le type d'objet correspondant à int. Lorsqu'on shortlui demande s'il correspond à l'objet passé, il observera que l'objet en question est un intplutôt qu'un a shortet conclura ainsi qu'il ne peut pas être égal.

En général, même si le compilateur ne s'en plaindra pas, il faut éviter de comparer des choses qui ne sont pas du même type; si l'on veut savoir si la conversion des choses en une forme commune donnerait le même résultat, on devrait effectuer une telle conversion explicitement. Considérez, par exemple,

int i = 16777217;
float f = 16777216.0f;

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

Il existe trois manières de comparer un intà un float. On pourrait vouloir savoir:

  1. La floatvaleur la plus proche possible du intcorrespond-elle à la float?
  2. Est-ce que la partie du nombre entier floatcorrespond au int?
  3. Faites intet floatreprésentez la même valeur numérique.

Si l'on essaie de comparer un intet floatdirectement, le code compilé répondra à la première question; si c'est ce que le programmeur a voulu, cependant, sera loin d'être évident. Changer la comparaison en (float)i == findiquerait clairement que le premier sens était voulu, ou (double)i == (double)famènerait le code à répondre à la troisième question (et indiquerait clairement que c'est ce qui était prévu).

(*) Même si la spécification C # considère une valeur de type par exemple System.Int32comme étant un objet de type System.Int32, une telle vue est contredite par l'exigence qu'un code s'exécute sur une plate-forme dont la spécification considère les valeurs et les objets comme habitant des univers différents. De plus, si Test un type de référence, et xest a T, alors une référence de type Tdevrait pouvoir faire référence x. Ainsi, si une variable vde type Int32contient an Object, une référence de type Objectdoit pouvoir contenir une référence à vou à son contenu. En fait, une référence de type Objectpourrait pointer vers un objet contenant des données copiées v, mais pas vers vlui-même ni vers son contenu. Cela suggérerait que nivni son contenu n'est vraiment un Object.

supercat
la source
1
the only implicit conversion which would satisfy an overload of the Equals method would be the conversion to the object type corresponding to intFaux. Contrairement à Java, C # n'a pas de types primitifs et encadrés séparés. Il est encadré objectparce que c'est la seule autre surcharge de Equals().
SLaks
La première et la troisième question sont identiques; la valeur exacte a déjà été perdue lors de la conversion en float. Lancer un floaten un doublene créera pas comme par magie une nouvelle précision.
SLaks
@SLaks: selon la spécification ECMA, qui décrit la machine virtuelle sur laquelle s'exécute C #, chaque définition de type de valeur crée deux types distincts. La spécification C # peut dire que le contenu d'un emplacement de stockage de type List<String>.Enumeratoret d'un objet de tas de type List<String>.Enumeratorsont identiques, mais la spécification ECMA / CLI indique qu'ils sont différents, et même lorsqu'ils sont utilisés en C #, ils se comportent différemment.
supercat du
@SLaks: Si iet fétaient chacun convertis doubleavant la comparaison, ils donneraient 16777217.0 et 16777216.0, qui se comparent comme inégaux. La conversion i floatdonnerait 16777216.0f, comparant égal à f.
supercat du
@SLaks: pour un exemple simple de la différence entre les types d'emplacement de stockage et les types d'objets encadrés, considérez la méthode bool SelfSame<T>(T p) { return Object.ReferenceEquals((Object)p,(Object)p);}. Le type d'objet encadré correspondant à un type valeur peut satisfaire le type de paramètre ReferenceEqualsvia une conversion ascendante préservant l'identité ; le type d'emplacement de stockage, cependant, nécessite une conversion sans préservation d'identité . Si lancer un Tto Urenvoie une référence à autre chose que l'original T, cela me suggère que a Tn'est pas vraiment un U.
supercat du
5

Equals () est une méthode de System.Object Class
Syntaxe: Public virtual bool Equals ()
Recommandation si nous voulons comparer l'état de deux objets, nous devrions utiliser la méthode Equals ()

comme indiqué ci-dessus, les réponses == opérateurs comparent les valeurs sont les mêmes.

Veuillez ne pas vous confondre avec ReferenceEqual

Reference Equals ()
Syntaxe: public static bool ReferenceEquals ()
Détermine si l'instance des objets spécifiés est de la même instance

Sugat Mankar
la source
8
Cela ne répond pas du tout à la question.
SLaks
SLaks i dnt expliqué avec des exemples ce sont les bases de la question ci-dessus.
Sugat Mankar
4

Ce que vous devez réaliser, c'est que faire ==finira toujours par appeler une méthode. La question est de savoir si appeler ==et Equalsfinit par appeler / faire les mêmes choses.

Avec les types de référence, ==vérifiera toujours d'abord si les références sont identiques ( Object.ReferenceEquals). Equalsd'autre part, peut être remplacé et peut vérifier si certaines valeurs sont égales.

EDIT: pour répondre à svick et ajouter le commentaire SLaks, voici un code 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.
utilisateur276648
la source
Alors quelle méthode la comparaison de deux ints avec == appelle-t-elle? Astuce: il n'y a pas de operator ==méthode pour Int32, mais il y en a une pourString .
svick
2
Cela ne répond pas du tout à la question.
SLaks
@SLaks: il ne répond en effet pas à la question spécifique sur l'int et la comparaison courte, vous y avez déjà répondu. Je pense toujours qu'il est intéressant d'expliquer que ==cela ne fait pas que de la magie, cela appelle finalement simplement une méthode (la plupart des programmeurs n'ont probablement jamais implémenté / remplacé aucun opérateur). J'aurais peut-être pu ajouter un commentaire à votre question au lieu d'ajouter ma propre réponse. N'hésitez pas à mettre à jour le vôtre si vous pensez que ce que j'ai dit est pertinent.
user276648
Notez que ==sur les types primitifs, ce n'est pas un opérateur surchargé, mais une fonctionnalité de langage intrinsèque qui se compile à l' ceqinstruction IL.
SLaks
3

== En primitif

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

Dans la comparaison primitive, l'opérateur == se comporte de manière assez évidente, en C # il existe de nombreuses surcharges d'opérateur == disponibles.

  • chaîne == chaîne
  • int == int
  • uint == uint
  • long == long
  • beaucoup plus

Donc, dans ce cas, il n'y a pas de conversion implicite de intenshort mais shortvers intest possible. Ainsi, newAge est converti en int et la comparaison se produit, ce qui renvoie true car les deux ont la même valeur. Donc c'est équivalent à:

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

.Equals () en primitif

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

Ici, nous devons voir ce qu'est la méthode Equals (), nous appelons Equals avec une variable de type court. Il y a donc trois possibilités:

  • Equals (objet, objet) // méthode statique de l'objet
  • Equals (object) // méthode virtuelle de l'objet
  • Equals (short) // Implémente IEquatable.Equals (short)

Le premier type n'est pas le cas ici car le nombre d'arguments est différent que nous appelons avec un seul argument de type int. Third est également éliminé comme mentionné ci-dessus, la conversion implicite de int en short n'est pas possible. Donc ici le deuxième type de Equals(object)est appelé. Le short.Equals(object)est:

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

Donc, ici, la condition a été testée, z is shortce qui est faux car z est un int donc il retourne faux.

Voici l'article détaillé d'Eric Lippert

Zaheer Ahmed
la source