Indications concernant la substitution de Equals() et de l'opérateur == (Guide de programmation C#)
Mise à jour : novembre 2007
En C#, il y a deux types différents d'égalité : l'égalité des références et l'égalité des valeurs. L'égalité des valeurs signifie simplement que deux objets contiennent les mêmes valeurs. Par exemple, deux entiers avec la valeur 2 ont une égalité des valeurs. L'égalité des références signifie qu'il n'y a pas deux objets à comparer. Il y a plutôt deux références d'objet, qui se réfèrent toutes deux au même objet. Cela peut se produire par simple assignation, comme illustré dans l'exemple suivant :
System.Object a = new System.Object();
System.Object b = a;
System.Object.ReferenceEquals(a, b); //returns true
Dans ce code, un seul objet existe, mais il y a plusieurs références à cet objet : a et b. Parce qu'elles font toutes deux référence au même objet, elles ont l'égalité des références. Si deux objets ont l'égalité des références, ils ont également l'égalité des valeurs, mais l'égalité des valeurs ne garantit pas l'égalité des références.
Pour vérifier l'égalité des références, utilisez ReferenceEquals. Pour vérifier l'égalité des valeurs, utilisez Equals.
Substitution des égaux
Comme Equals est une méthode virtuelle, n'importe quelle classe peut substituer son implémentation. Toute classe qui représente une valeur, essentiellement n'importe quel type valeur, ou un jeu de valeurs formant un groupe, tel qu'une classe de nombre complexe, doit substituer Equals. Si le type implémente IComparable, il doit substituer Equals.
La nouvelle implémentation de Equals doit suivre toutes les garanties de Equals :
x.Equals(x) retourne la valeur true.
x.Equals(y) retourne la même valeur que y.Equals(x).
si (x.Equals(y) && y.Equals(z)) retourne la valeur true, alors x.Equals(z) retourne la valeur true.
Les appels successifs de x.Equals(y) retournent la même valeur tant que les objets référencés par x et y ne sont pas modifiés.
x. Equals (null) retourne la valeur false (uniquement pour les types valeur n'autorisant pas les valeur Null). Pour plus d'informations, consultez Types nullables (Guide de programmation C#).
La nouvelle implémentation de Equals ne doit pas lever d'exceptions. Il est recommandé que toute classe qui substitue Equals substitue également Object.GetHashCode. Il est également recommandé qu'en plus d'implémenter Equals(objet), toute classe implémente également Equals(type) pour leur propre type, afin d'améliorer la performance. Par exemple :
class TwoDPoint : System.Object
{
public readonly int x, y;
public TwoDPoint(int x, int y) //constructor
{
this.x = x;
this.y = y;
}
public override bool Equals(System.Object obj)
{
// If parameter is null return false.
if (obj == null)
{
return false;
}
// If parameter cannot be cast to Point return false.
TwoDPoint p = obj as TwoDPoint;
if ((System.Object)p == null)
{
return false;
}
// Return true if the fields match:
return (x == p.x) && (y == p.y);
}
public bool Equals(TwoDPoint p)
{
// If parameter is null return false:
if ((object)p == null)
{
return false;
}
// Return true if the fields match:
return (x == p.x) && (y == p.y);
}
public override int GetHashCode()
{
return x ^ y;
}
}
Toute classe dérivée qui peut appeler Equals sur la classe de base doit le faire avant de terminer sa comparaison. Dans l'exemple suivant, Equals appelle la classe de base Equals, qui vérifie s'il se trouve un paramètre null et compare le type du paramètre avec le type de la classe dérivée. Cela laisse à l'implémentation de Equals sur la classe dérivée la tâche de vérifier le nouveau champ de données déclaré sur la classe dérivée :
class ThreeDPoint : TwoDPoint
{
public readonly int z;
public ThreeDPoint(int x, int y, int z)
: base(x, y)
{
this.z = z;
}
public override bool Equals(System.Object obj)
{
// If parameter cannot be cast to ThreeDPoint return false:
ThreeDPoint p = obj as ThreeDPoint;
if ((object)p == null)
{
return false;
}
// Return true if the fields match:
return base.Equals(obj) && z == p.z;
}
public bool Equals(ThreeDPoint p)
{
// Return true if the fields match:
return base.Equals((TwoDPoint)p) && z == p.z;
}
public override int GetHashCode()
{
return base.GetHashCode() ^ z;
}
}
Substitution de l'opérateur ==
Par défaut, l'opérateur == vérifie l'égalité des références en déterminant si deux références indiquent le même objet. Ainsi, les types référence n'ont pas besoin d'implémenter l'opérateur == pour gagner cette fonctionnalité. Lorsqu'un type est immuable, ce qui signifie que les données contenues dans l'instance ne peuvent pas être changées, il peut être utile de surcharger l'opérateur == pour comparer l'égalité des valeurs au lieu de l'égalité des références car, en tant qu'objets immuables, ils peuvent être considérés comme identiques tant qu'ils ont la même valeur. La substitution de l'opérateur == dans les types non immuables n'est pas recommandée.
Les implémentations d'opérateurs == surchargés ne doivent pas lever d'exceptions. Tout type qui surcharge l'opérateur == doit également surcharger l'opérateur !=. Par exemple :
//add this code to class ThreeDPoint as defined previously
//
public static bool operator ==(ThreeDPoint a, ThreeDPoint b)
{
// If both are null, or both are same instance, return true.
if (System.Object.ReferenceEquals(a, b))
{
return true;
}
// If one is null, but not both, return false.
if (((object)a == null) || ((object)b == null))
{
return false;
}
// Return true if the fields match:
return a.x == b.x && a.y == b.y && a.z == b.z;
}
public static bool operator !=(ThreeDPoint a, ThreeDPoint b)
{
return !(a == b);
}
Remarque : |
---|
Une erreur courante dans les surcharges de l'opérateur == est d'utiliser (a == b), (a == null) ou (b == null) pour vérifier l'égalité des références. Cela crée à la place un appel à l'opérateur surchargé ==, ce qui provoque une boucle infinie. Utilisez ReferenceEquals ou effectuez un cast du type en Object, pour éviter la boucle. |
Voir aussi
Concepts
Référence
Instructions, expressions et opérateurs (Guide de programmation C#)
Opérateurs (Guide de programmation C#)