web-dev-qa-db-fra.com

Quelle est la difference entre double? et int? pour les comparaisons .Equals?

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.

8
Iteria

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:

  • Si les deux opérandes sont nuls, ils sont égaux
  • Si l'un est nul et l'autre ne l'est pas, ils sont inégaux
  • Si les deux ne sont pas nuls, retournez à la casse non nullable ci-dessus.

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:

  • Si le destinataire est null et que l'argument est null, ils sont égaux
  • Si le destinataire n'est pas nul, alors vous vous en remettez à la sémantique non nullable de 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:

  • Réflexivité: une chose est égale à elle-même. C’est généralement vrai en C # bien qu’il y ait quelques exceptions.
  • Symétrie: Si A est égal à B, alors B est égal à A. Cela est vrai de == en C # mais pas de A.Equals(B), comme nous l'avons vu.
  • Transitivité: Si A est égal à B et B est égal à C, A est également égal à C. Ce n'est absolument pas le cas dans C #.

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?

14
Eric Lippert

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;
}
1
John Boker