J'ai une situation très étrange que je ne comprends pas. Ci-dessous le cas simplifié:
double? d = 2;
int? i = 2;
Console.WriteLine(d.Equals((2))); // false
Console.WriteLine(i.Equals((2))); // true
Je ne comprends pas pourquoi une expression me dira vrai et une autre fausse. Ils semblent identiques.
Vous avez tout à fait raison de trouver cela déroutant. C'est le bordel.
Commençons par dire clairement ce qui se passe en regardant plus d'exemples, puis nous en déduirons les règles correctes appliquées ici. Étendons votre programme pour prendre en compte tous ces cas:
double d = 2;
double? nd = d;
int i = 2;
int? ni = i;
Console.WriteLine(d == d);
Console.WriteLine(d == nd);
Console.WriteLine(d == i);
Console.WriteLine(d == ni);
Console.WriteLine(nd == d);
Console.WriteLine(nd == nd);
Console.WriteLine(nd == i);
Console.WriteLine(nd == ni);
Console.WriteLine(i == d);
Console.WriteLine(i == nd);
Console.WriteLine(i == i);
Console.WriteLine(i == ni);
Console.WriteLine(ni == d);
Console.WriteLine(ni == nd);
Console.WriteLine(ni == i);
Console.WriteLine(ni == ni);
Console.WriteLine(d.Equals(d));
Console.WriteLine(d.Equals(nd));
Console.WriteLine(d.Equals(i));
Console.WriteLine(d.Equals(ni)); // False
Console.WriteLine(nd.Equals(d));
Console.WriteLine(nd.Equals(nd));
Console.WriteLine(nd.Equals(i)); // False
Console.WriteLine(nd.Equals(ni)); // False
Console.WriteLine(i.Equals(d)); // False
Console.WriteLine(i.Equals(nd)); // False
Console.WriteLine(i.Equals(i));
Console.WriteLine(i.Equals(ni));
Console.WriteLine(ni.Equals(d)); // False
Console.WriteLine(ni.Equals(nd)); // False
Console.WriteLine(ni.Equals(i));
Console.WriteLine(ni.Equals(ni));
Tous ces éléments sont vrais, à l'exception de ceux que j'ai notés comme faux.
Je vais maintenant donner une analyse de ces cas.
La première chose à noter est que l'opérateur ==
dit toujours True
. Pourquoi donc?
La sémantique de ==
non nullable est la suivante:
int == int -- compare the integers
int == double -- convert the int to double, compare the doubles
double == int -- same
double == double -- compare the doubles
Ainsi, dans tous les cas non nullables, le nombre entier 2 est égal au double 2.0, car l'int 2 est converti en double 2.0 et la comparaison est vraie.
La sémantique de nullable ==
est la suivante:
Encore une fois, nous voyons que pour les comparaisons nullables, int? == double?
, int? == double
, etc., nous retombons toujours dans les cas non nullables, convertissons le int?
en double
et faisons la comparaison en double. Ainsi, tout cela est également vrai.
Nous arrivons maintenant à Equals
, où les choses se gâchent.
Il y a un problème de conception fondamental ici, sur lequel j'ai écrit en 2009: https://blogs.msdn.Microsoft.com/ericlippert/2009/04/09/double-your-dispatch-double-your-fun/ - Le problème est que la signification de ==
est résolue en fonction des types de temps de compilation des deux opérandes. Mais Equals
est résolu sur la base du type d'exécution de l'opérande à gauche (le destinataire), mais le type de compilation du à droite)} opérande (l'argument), et c'est pourquoi les choses se passent mal sur les rails.
Commençons par regarder ce que double.Equals(object)
fait. Si le destinataire d'un appel à Equals(object)
est double
, alors si l'argument n'est pas un double encadré, ils sont considérés comme non égaux. C'est-à-dire que Equals
nécessite que les types correspondance, alors que ==
nécessite que les types soient convertibles en un type commun.
Je vais le répéter. double.Equals
fait pas essayer de convertir son argument en double, contrairement à ==
. Elle vérifie simplement si elle double déjà est, et si ce n'est pas le cas, elle indique qu'ils ne sont pas égaux.
Cela explique pourquoi d.Equals(i)
est faux ... mais ... attendez une minute, c’est pas faux ci-dessus! Qu'est-ce qui explique ça?
double.Equals
est surchargé! En haut, nous appelons en fait double.Equals(double)
, qui - vous l’avez deviné - convertit l’int en double avant de faire l’appel! Si nous avions dit d.Equals((object)i))
, alors ce serait faux.
Très bien, nous savons donc pourquoi double.Equals(int)
est vrai - parce que l'int est converti en double.
Nous savons également pourquoi double.Equals(int?)
est faux. int?
n'est pas convertible en double, mais en object
. Nous appelons donc double.Equals(object)
et boxons la int
, et maintenant ce n'est pas égal.
Qu'en est-il de nd.Equals(object)
? La sémantique de cela est:
d.Equals(object)
Nous savons donc maintenant pourquoi nd.Equals(x)
fonctionne si x
est une double
ou double?
, mais pas si elle est int
ou int?
. (Bien qu'intéressant, bien sûr, (default(double?)).Equals(default(int?))
serait vrai car ils sont tous deux nuls!)
Enfin, par une logique similaire, nous voyons pourquoi int.Equals(object)
donne le comportement qu’il a. Il vérifie si son argument est un entier encadré, et si ce n'est pas le cas, il renvoie false. Donc i.Equals(d)
est faux. La i
ne peut pas être convertie en double et la d
ne peut pas être convertie en int.
C'est un énorme gâchis. Nous voudrions que égalité soit un relation d'équivalence, et ce n'est pas! Une relation d'égalité doit avoir les propriétés suivantes:
==
en C # mais pas de A.Equals(B)
, comme nous l'avons vu.Donc, c'est un gâchis à tous les niveaux. ==
et Equals
ont des mécanismes d’envoi différents et donnent des résultats différents, aucun d’eux n’est une relation d’équivalence, et tout cela prête à confusion. Toutes mes excuses pour vous avoir mis dans ce pétrin, mais c’était désordonné quand je suis arrivé.
Pour une interprétation légèrement différente de la raison pour laquelle l’égalité est terrible en C #, voir le point 9 de ma liste de décisions linguistiques regrettables, ici: http://www.informit.com/articles/article.aspx?p=2425867
BONUS EXERCICE: Répétez l'analyse ci-dessus, mais pour x?.Equals(y)
pour les cas où x
est nullable. Quand obtenez-vous les mêmes résultats que pour les récepteurs non nullables et quand obtenez-vous des résultats différents?
Il semble que la réponse se trouve dans le source de la méthode Equals
pour chacun des types. Si les types ne correspondent pas, ils ne sont pas égaux.
https://referencesource.Microsoft.com/#mscorlib/system/double.cs,147
// True if obj is another Double with the same value as the current instance. This is
// a method of object equality, that only returns true if obj is also a double.
public override bool Equals(Object obj) {
if (!(obj is Double)) {
return false;
}
double temp = ((Double)obj).m_value;
// This code below is written this way for performance reasons i.e the != and == check is intentional.
if (temp == m_value) {
return true;
}
return IsNaN(temp) && IsNaN(m_value);
}
https://referencesource.Microsoft.com/#mscorlib/system/int32.cs,72
public override bool Equals(Object obj) {
if (!(obj is Int32)) {
return false;
}
return m_value == ((Int32)obj).m_value;
}