Parcours
/
C#
C#
/
Programme
/
Égalité
Ég

Égalité en C#

5 exercices

À propos de Égalité

L'exercice de code illustre un certain nombre de propriétés de l'égalité en C# :

Object.Equals()

Les thèmes abordés par l'exercice de code

  • Les types simples (strings et primitives) sont généralement testés pour l'égalité avec les opérateurs == et !=. C'est considéré comme plus idiomatique que d'utiliser la méthode Equals(), qui est elle aussi disponible pour ces types. Les développeurs Java doivent être attentifs, lorsqu'ils manipulent des strings, au fait que == compare par valeur en C# mais par référence en Java, quand ils reviennent à leur ancien langage.
  • Les types référence (les instances de classes) sont comparés à l'aide de la méthode Equals() héritée de object. Si ton objectif, avec le test d'égalité, est de t'assurer que deux objets sont exactement la même instance, alors s'appuyer sur l'implémentation de object suffit. Sinon, tu dois redéfinir object.Equals().
  • Si tu sais que toutes les instances de ta classe sont créées au même endroit, par exemple les personnages d'un jeu ou d'une simulation, alors l'égalité de référence suffit. Cependant, il est probable que plusieurs instances d'une même entité du monde réel soient créées (par exemple depuis une base de données, à partir d'une saisie utilisateur, via une requête web). Dans ce cas, les valeurs qui identifient l'entité de manière unique doivent être testées pour l'égalité. Par conséquent, Equals() doit être redéfinie.
  • Une Equals() redéfinie contiendra des tests d'égalité sur les membres de types simples à l'aide de ==, et sur les types référence avec des appels récursifs à Equals().
class StatusBar
{
    private readonly int width = 200, height = 20;

    public override bool Equals(object other)
    {
        // ... null and type checks and performance optimisations
        return width == (other as StatusBar).width && height == (other as StatusBar).height;
    }
}
class Window
{
    private readonly string title = "Main";
    private readonly StatusBar statusBar = new StatusBar();

    public override bool Equals(object other)
    {
        // ... null and type checks and performance optimisations
        return title == (other as Window).title && statusBar.Equals((other as Window).statusBar);
    }
}

  • La méthode statique object.ReferenceEquals() sert à comparer deux objets pour détecter s'il s'agit d'une seule et même instance. Cela apporte de la clarté et devient une nécessité lorsque Equals() et == ont été redéfinies ou surchargées.
var winA = new Window(); // above code shows that all windows are equal
var winB = new Window();
ReferenceEquals(winA, winB);
// => false
var winC = winA;
ReferenceEquals(winA, winC);
// => true

Thèmes annexes

  • En plus de public override bool Equals(object obj), les IDE génèrent généralement la surcharge protected bool Equals(FacialFeatures other) à utiliser lorsque l'héritage entre en jeu. Une classe dérivée peut appeler la méthode Equals() de la classe de base, puis ajouter son propre test.
  • N'utilise pas == à moins d'avoir surchargé l'opérateur == ainsi que la méthode Equals() de ta classe (voir l'exercice operator-overloading), ou à moins que seule l'égalité des références t'importe.
  • Les tests d'égalité dans les structs sont traités dans l'exercice structs.
  • L'égalité des tuples est traitée dans l'exercice tuples.
  • De nombreux développeurs s'appuient sur leur IDE pour fournir une implémentation des méthodes d'égalité, car celui-ci prend en charge toutes les subtilités de l'égalité. Par exemple, RIDER de JetBrains (v 2020.1) génère les méthodes d'égalité suivantes pour une classe :
protected bool Equals(T other)
{
    return field1 == other.field1 && field2.Equals(other.field2);
}

public override bool Equals(object obj)
{
    if (ReferenceEquals(null, obj)) return false;
    if (ReferenceEquals(this, obj)) return true;
    if (obj.GetType() != this.GetType()) return false;
    return Equals((T) obj);
}
  • Fais attention si tu décides d'améliorer le code généré par ton IDE. Ce code est généralement à toute épreuve face aux valeurs nulles et aux objets mal typés, et plutôt bien optimisé.
  • Les tests d'égalité des délégués ne sont pas abordés spécifiquement dans l'exercice.
  • Il n'existe pas de tests d'égalité intégrés pour les tableaux ni pour la plupart des collections. LINQ (abordé dans des exercices ultérieurs) fournit SequenceEquals(), mais en l'absence de LINQ, il faut parcourir les deux collections et comparer les éléments un par un.
  • Pour une discussion sur l'utilisation de == et != avec tes propres classes, voir l'exercice operator-overloading.

Object.GetHashCode()

  • object.GetHashCode() renvoie un code de hachage sous la forme d'un entier de 32 bits. Le code de hachage est utilisé par les classes dictionnaire et ensemble telles que Dictionary<T> et HashSet<T> pour stocker et récupérer des objets de manière performante. Dans le cas des dictionnaires, le hachage concerne les clés.
  • Les développeurs C# s'attendent à ce que, si tu redéfinis Equals(), tu redéfinisses aussi GetHashCode(). Il existe une relation entre Equals() et GetHashCode() qui doit être respectée pour que les classes dictionnaire et ensemble de hachage, ainsi que toutes celles qui utilisent un code de hachage, se comportent correctement. Tu es censé implémenter la méthode de telle sorte à ne pas tendre de pièges aux mainteneurs qui pourraient ajouter plus tard une collection basée sur un code de hachage.
  • La relation entre code de hachage et égalité est la suivante : si deux objets sont égaux (Equal() renvoie true), alors GetHashCode() doit renvoyer la même valeur pour ces deux objets. Cela ne s'applique pas dans le sens inverse. La relation n'est pas symétrique. Imagine une fonction de recherche qui se rend d'abord dans un « compartiment » en fonction du code de hachage, puis sélectionne l'élément particulier à l'aide du test d'égalité.
  • Le moyen le plus simple de créer un code de hachage est d'appeler HashCode.Combine() en lui passant les valeurs utilisées dans le test d'égalité (ou un sous-ensemble). Garde à l'esprit que plus tu fournis d'informations à Combine(), plus l'implémentation du hachage sera performante.
public class Assessment
{
    private int rating;
    private Person boss;

    public override int GetHashCode()
    {
        return HashCode.Combine(rating, boss);
    }
}
  • Les valeurs utilisées dans le test d'égalité doivent rester stables tant que la collection hachée est utilisée. Si tu ajoutes un objet à la collection avec un jeu de valeurs, puis que tu modifies ces valeurs, le code de hachage ne pointera plus vers le bon « compartiment ». En pratique, cela signifie que l'objet doit être immuable. Les autres approches risquent de créer des pièges pour les mainteneurs. L'immuabilité est abordée dans d'autres exercices.
  • Il est possible de concevoir un meilleur code de hachage que celui produit par les routines de la bibliothèque, mais c'est soit parce que tu as une compréhension détaillée des caractéristiques des données, soit parce qu'il s'agit d'une collection très simple où les valeurs peuvent être utilisées directement sans hachage. Cela ne vaut peut-être pas l'effort supplémentaire.

Améliorations de performance

Pour améliorer légèrement les performances, en particulier lorsque les objets appartiennent à des collections, tu peux ajouter un membre surchargé public bool Equals(T other).

Cela économisera un certain nombre de vérifications de type pour les types référence et économisera une étape de boxing pour les types valeur, car ils n'auront pas besoin d'être convertis en objet (boxed) comme argument de public override bool Equals(object other).

Si tu ajoutes l'interface IEquatable<T> à ta classe, cela t'obligera à implémenter la surcharge. À moins que ton code ne contienne des routines qui prennent des objets de type IEquatable<T> (et qui font sans doute quelque chose d'intéressant en rapport avec l'égalité, indépendamment de la classe qui l'implémente), il n'y a vraiment aucune autre raison impérieuse d'inclure cette interface.

IEqualityComparer<T>

Si tu as une classe qui peut être identifiée de manière unique de deux façons différentes, par exemple une classe Person qui possède un SSID et une adresse e-mail unique, alors .NET fournit un moyen de permettre à deux collections différentes d'utiliser des tests de code de hachage et d'égalité différents. Chacune peut avoir une implémentation différente de IEqualityComparer<T> avec sa propre méthode Equals() et sa propre méthode GetHashCode(). Tu peux avoir un dictionnaire indexé par SSID et un autre indexé par adresse e-mail.

Lorsque IEqualityComparer<T> entre en jeu, tu redéfinis généralement quand même Equals() et GetHashCode() sur ta classe d'élément pour éviter des problèmes en dehors des classes de collection.

Une considération, lorsque tu utilises IEqualityComparer<T>, est que les méthodes privées, etc., ne seront pas disponibles pour le test d'égalité.

Si une seule collection hachée est en jeu, il peut être préférable d'éviter IEqualityComparer<T> et d'encapsuler l'égalité et le code de hachage dans l'objet lui-même. Il n'est déjà pas idéal qu'une dépendance essentielle comme celle entre un objet et une collection hachée ne puisse pas être imposée par le compilateur, mais si la logique de hachage et de comparaison se trouve ailleurs, il serait encore plus facile pour un mainteneur de modifier les membres d'une classe sans se soucier des conséquences pour la collection.

Note sur l'égalité en virgule flottante

Une primitive qui peut déstabiliser le développeur imprudent est le test de l'égalité des valeurs à virgule flottante. Ce sujet est abordé dans le document about.md du concept floating-point-numbers.

Égalité et héritage

Cet article présente quelques-unes des décisions à prendre en matière d'égalité et d'héritage. Elles ne te concerneront que si tu manipules des instances d'une classe et d'une classe dérivée et que tu dois tester l'égalité entre les deux.

Modifie via GitHub Le lien s'ouvre dans une nouvelle fenêtre ou un nouvel onglet

Apprends Égalité

L'entraînement est verrouillé

Déverrouille 4 exercices de plus pour t'entraîner sur Égalité