48 votes

Quelle est la "meilleure pratique" pour comparer deux instances d'un type de référence ?

Je suis tombé sur ce problème récemment. Jusqu'à présent, je me contentais de remplacer l'opérateur d'égalité ( \== ) et/ou Est égal à afin de vérifier si deux types de références contiennent effectivement la même chose. données (c'est-à-dire deux instances différentes qui se ressemblent).

Je l'utilise encore plus depuis que j'ai commencé à m'intéresser aux tests automatisés (comparaison des données de référence/attendues avec celles qui sont renvoyées).

En regardant certains des directives sur les normes de codage dans MSDN Je suis tombé sur un article qui le déconseille. Je comprends maintenant pourquoi l'article dit cela (parce que ce ne sont pas les mêmes instance ) mais cela ne répond pas à la question :

  1. Quelle est la meilleure façon de comparer deux types de référence ?
  2. Devrions-nous mettre en œuvre IComparable ? (J'ai également vu que cela devrait être réservé aux types de valeurs).
  3. Y a-t-il une interface que je ne connais pas ?
  4. Devrions-nous simplement rouler le nôtre ? !

Merci beaucoup ^_^

Mise à jour

Il semble que j'ai mal lu une partie de la documentation (la journée a été longue) et que le fait d'écraser Est égal à peut être la voie à suivre

Si vous mettez en œuvre la référence vous devez envisager de remplacer la méthode Equals sur un type de référence si votre type ressemble à un type de base tel qu'un Point, String, BigNumber, et ainsi de suite. La plupart des types de référence ne devraient pas ne devraient pas surcharger la méthode égalité opérateur, même s'ils remplacent Equals . Cependant, si vous implémentez un type de référence qui est destiné à avoir une sémantique de valeur comme un type de nombre complexe complexe, vous devez remplacer l'opérateur d'égalité d'égalité.

3 votes

"La plupart des types de référence ne doivent pas surcharger l'opérateur d'égalité, même s'ils surchargent Equals" ? Wow, je trouve ça un peu... hum... bizarre. Ainsi, a.Equals(b) pourrait être vrai, et a==b pourrait être faux. Si je veux savoir si les références sont égales (ce qui est rarement le cas, honnêtement), j'utiliserais de toute façon .ReferenceEquals(a,b). J'aimerais que a==b renvoie la même chose que a.Equals(b). N'est-ce pas une "meilleure pratique" ?

0 votes

@FlipScript : Un problème majeur avec le remplacement de l'option == est qu'il s'agit en fait de deux opérateurs ; lorsqu'il est utilisé avec des types pour lesquels des surcharges existent, il utilise la surcharge ; sinon, si les opérandes sont des types de référence, il s'agit d'une vérification de l'égalité des références. Depuis == est lié de manière statique plutôt que virtuelle, même lorsqu'il est utilisé avec des génériques, ce comportement peut entraîner des résultats inattendus. En vb.net, des opérateurs distincts sont utilisés pour l'égalité dérogatoire et l'égalité de référence, ce qui évite une telle ambiguïté.

26voto

Konrad Rudolph Points 231505

Mise en œuvre de l'égalité dans .NET de manière correcte, efficace et transparente. sans duplication de code est difficile. Plus précisément, pour les types de référence avec une sémantique de valeur (c.-à-d. des types immuables qui traitent l'équvialence comme une égalité ), vous devez mettre en œuvre le site System.IEquatable<T> interface et vous devez mettre en œuvre toutes les différentes opérations ( Equals , GetHashCode y == , != ).

À titre d'exemple, voici une classe implémentant l'égalité des valeurs :

class Point : IEquatable<Point> {
    public int X { get; }
    public int Y { get; }

    public Point(int x = 0, int y = 0) { X = x; Y = y; }

    public bool Equals(Point other) {
        if (other is null) return false;
        return X.Equals(other.X) && Y.Equals(other.Y);
    }

    public override bool Equals(object obj) => Equals(obj as Point);

    public static bool operator ==(Point lhs, Point rhs) => object.Equals(lhs, rhs);

    public static bool operator !=(Point lhs, Point rhs) => ! (lhs == rhs);

    public override int GetHashCode() => X.GetHashCode() ^ Y.GetHashCode();
}

Les seules parties mobiles dans le code ci-dessus sont les parties en gras : la deuxième ligne de la section Equals(Point other) et le GetHashCode() méthode. Le reste du code doit rester inchangé.

Pour les classes de référence qui ne représentent pas des valeurs immuables, n'implémentez pas les opérateurs == y != . Au lieu de cela, utilisez leur signification par défaut, qui est de comparer l'identité des objets.

Le code intentionnellement équivaut même aux objets d'un type de classe dérivée. Souvent, cela n'est pas souhaitable car l'égalité entre la classe de base et les classes dérivées n'est pas bien définie. Malheureusement, .NET et les directives de codage ne sont pas très claires à ce sujet. Le code que Resharper crée, affiché dans une autre réponse est susceptible d'avoir un comportement indésirable dans de tels cas car Equals(object x) y Equals(SecurableResourcePermission x) sera traiter cette affaire différemment.

Afin de modifier ce comportement, un contrôle de type supplémentaire doit être inséré dans l'élément fortement typé Equals méthode ci-dessus :

public bool Equals(Point other) {
    if (other is null) return false;
    if (other.GetType() != GetType()) return false;
    return X.Equals(other.X) && Y.Equals(other.Y);
}

0 votes

Bonne réponse Konrad, trop bonne pour cette question vraiment (c'est-à-dire que je n'ai vraiment besoin que du côté égalité des choses) ! Bonne réponse cependant, +1 de ma part :)

2 votes

Pour les classes, pourquoi surcharger les opérateurs d'égalité et d'inégalité pour effectuer une comparaison de référence, alors que cette fonctionnalité est fournie par défaut par la classe de base System.Object ?

0 votes

@Burly : Je ne sais pas. Ils devraient jouer valeur comparaison (remarquez la lhs.Equals(rhs) à la fin) ! Le site ReferenceEquals ne sont qu'un gain de temps (dans certaines circonstances) car si deux références sont égales, il n'est pas nécessaire de vérifier l'égalité des valeurs (x == x, toujours).

23voto

Matt J Points 15475

Il semble que vous codiez en C#, qui dispose d'une méthode appelée Equals que votre classe devrait implémenter, si vous souhaitez comparer deux objets en utilisant une autre métrique que "ces deux pointeurs (parce que les handles d'objets ne sont que cela, des pointeurs) vers la même adresse mémoire".

J'ai récupéré un exemple de code dans ici :

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

Java dispose de mécanismes très similaires. Le site equals() fait partie de la méthode Objet et votre classe la surcharge si vous voulez ce type de fonctionnalité.

La raison pour laquelle la surcharge de '==' peut être une mauvaise idée pour les objets est que, généralement, vous voulez toujours être en mesure de faire les comparaisons "est-ce le même pointeur". On s'appuie généralement sur ces comparaisons pour, par exemple, insérer un élément dans une liste où aucun doublon n'est autorisé, et certains éléments de votre framework peuvent ne pas fonctionner si cet opérateur est surchargé de manière non standard.

0 votes

Bonne réponse, merci. Je suis heureux que vous ayez ajouté la partie sur le pourquoi pas pour surcharger l'opérateur d'égalité.

4 votes

C'est en fait l'une des faiblesses du C#. Cependant, tant que l'implémenteur suit les directives, ce n'est pas un problème car la sémantique de la fonction == ne sera pas modifié pour des références égales. Pourtant, je me retrouve à utiliser object.ReferenceEquals dans des situations critiques en C# (VB a Is à la place).

1 votes

Vous ne devriez pas écrire la logique d'égalité à deux endroits. Je ne sais pas comment MS s'est trompé

16voto

Zach Burlingame Points 7232

J'ai résumé ci-dessous ce que vous devez faire lorsque vous implémentez IEquatable et j'ai fourni les justifications des différentes pages de documentation MSDN.


Résumé

  • Lorsque vous souhaitez tester l'égalité des valeurs (par exemple, lorsque vous utilisez des objets dans des collections), vous devez implémenter l'interface IEquatable, surcharger Object.Equals et GetHashCode pour votre classe.
  • Lorsque vous souhaitez tester l'égalité des références, vous devez utiliser operator==, operator!= et Objet.ReferenceEquals .
  • Vous ne devez remplacer l'opérateur== et l'opérateur!= que dans les cas suivants Types de valeurs et les types de référence immuables.

Justification

IEquatable

L'interface System.IEquatable est utilisée pour comparer l'égalité de deux instances d'un objet. Les objets sont comparés en fonction de la logique mise en œuvre dans la classe. La comparaison donne lieu à une valeur booléenne indiquant si les objets sont différents. Par opposition à l'interface System.IComparable, qui renvoie un nombre entier indiquant la différence entre les valeurs des objets.

L'interface IEquatable déclare deux méthodes qui doivent être surchargées. La méthode Equals contient l'implémentation permettant d'effectuer la comparaison réelle et de renvoyer true si les valeurs des objets sont égales, ou false si elles ne le sont pas. La méthode GetHashCode doit renvoyer une valeur de hachage unique qui peut être utilisée pour identifier de manière unique des objets identiques contenant des valeurs différentes. Le type d'algorithme de hachage utilisé est spécifique à l'implémentation.

Méthode IEquatable.Equals

  • Vous devez implémenter IEquatable pour vos objets afin de gérer la possibilité qu'ils soient stockés dans un tableau ou une collection générique.
  • Si vous implémentez IEquatable, vous devez également surcharger les implémentations de la classe de base de Object.Equals(Object) et GetHashCode afin que leur comportement soit cohérent avec celui de la méthode IEquatable.Equals.

Directives relatives à la substitution de Equals() et de l'opérateur == (Guide de programmation C#)

  • x.Equals(x) retourne vrai.
  • x.Equals(y) renvoie la même valeur que y.Equals(x)
  • si (x.Equals(y) && y.Equals(z)) renvoie la vérité, alors x.Equals(z) renvoie la vérité.
  • Les invocations successives 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) renvoie false (uniquement pour les types de valeurs non nulles. Pour plus d'informations, voir Types nuls (Guide de programmation C#) .)
  • La nouvelle implémentation de Equals ne devrait pas lever d'exceptions.
  • Il est recommandé que toute classe qui surcharge Equals surcharge également Object.GetHashCode.
  • Il est recommandé qu'en plus d'implémenter Equals(object), toute classe implémente également Equals(type) pour son propre type, afin d'améliorer les performances.

Par défaut, l'opérateur == teste l'égalité des références en déterminant si deux références indiquent le même objet. Par conséquent, les types de référence ne doivent pas mettre en œuvre l'opérateur == afin de bénéficier de cette fonctionnalité. Lorsqu'un type est immuable, c'est-à-dire que les données contenues dans l'instance ne peuvent pas être modifiées, la surcharge de l'opérateur == pour comparer l'égalité des valeurs au lieu de l'égalité des références peut être utile car, en tant qu'objets immuables, ils peuvent être considérés comme identiques tant qu'ils ont la même valeur. Ce n'est pas une bonne idée de remplacer l'opérateur == dans les types non immuables.

  • Les implémentations de l'opérateur surchargé == ne doivent pas lever d'exceptions.
  • Tout type qui surcharge l'opérateur == doit également surcharger l'opérateur !=.

\== Opérateur (Référence C#)

  • Pour les types de valeurs prédéfinis, l'opérateur d'égalité (==) renvoie vrai si les valeurs de ses opérandes sont égales, faux sinon.
  • Pour les types de référence autres que les chaînes de caractères, == renvoie vrai si ses deux opérandes font référence au même objet.
  • Pour le type chaîne de caractères, == compare les valeurs des chaînes de caractères.
  • Lorsque vous testez la nullité en utilisant des comparaisons == dans vos surcharges d'opérateur==, assurez-vous d'utiliser l'opérateur de la classe d'objet de base. Si vous ne le faites pas, une récursion infinie se produira, entraînant un débordement de pile.

Méthode Object.Equals (objet)

Si votre langage de programmation prend en charge la surcharge des opérateurs et si vous choisissez de surcharger l'opérateur d'égalité pour un type donné, ce type doit surcharger la méthode Equals. Ces implémentations de la méthode Equals doivent renvoyer les mêmes résultats que l'opérateur d'égalité.

Les directives suivantes concernent la mise en œuvre d'un type de valeur :

  • Envisagez de remplacer Equals pour obtenir des performances supérieures à celles fournies par l'implémentation par défaut de Equals sur ValueType.
  • Si vous surchargez Equals et que la langue prend en charge la surcharge des opérateurs, vous devez surcharger l'opérateur d'égalité pour votre type de valeur.

Les directives suivantes concernent la mise en œuvre d'un type de référence :

  • Envisagez de remplacer Equals sur un type de référence si la sémantique du type est basée sur le fait que le type représente une ou plusieurs valeurs.
  • La plupart des types de référence ne doivent pas surcharger l'opérateur d'égalité, même s'ils surchargent Equals. Toutefois, si vous implémentez un type de référence destiné à avoir une sémantique de valeur, comme un type de nombre complexe, vous devez surcharger l'opérateur d'égalité.

Autres problèmes

1 votes

L'utilisation du même nom pour Equals(Object) y Equals(OwnType) est peut-être malheureux, car dans de nombreux cas, à cause des typages implicites, ni l'un ni l'autre des Equals(OwnType) ni le == peut définir une relation d'équivalence. Si j'avais conçu .net, l'opérateur Object La méthode serait nommée EquivalentTo et les substitutions devraient utiliser des normes d'équivalence plus strictes. Par exemple, je spécifierais que 1.0m.EquivalentTo(1.00m) devrait être faux, mais 1.0m.Equals(1.00m) y 1.0m == 1.00m devrait être vrai puisque les valeurs sont numériquement égaux, même s'ils ne sont pas équivalent .

3voto

bdukes Points 54833

Cet article recommande simplement de ne pas surcharger l'opérateur d'égalité (pour les types de référence), mais pas de surcharger Equals. Vous devriez surcharger Equals dans votre objet (référence ou valeur) si les contrôles d'égalité signifient quelque chose de plus que les contrôles de référence. Si vous voulez une interface, vous pouvez aussi implémenter IEquatable (utilisé par les collections génériques). Toutefois, si vous implémentez IEquatable, vous devez également surcharger equals, comme l'indique la section des remarques sur IEquatable :

Si vous implémentez IEquatable<T>, vous devez également remplacer les implémentations de la classe de base de Object.Equals(Object) et GetHashCode afin que leur comportement soit cohérent avec celui de la méthode IEquatable<T>.Equals. Si vous surchargez Object.Equals(Object), votre implémentation surchargée est également appelée dans les appels à la méthode statique Equals(System.Object, System.Object) de votre classe. Cela garantit que toutes les invocations de la méthode Equals renvoient des résultats cohérents.

Pour ce qui est de savoir si vous devez mettre en œuvre Equals et/ou l'opérateur d'égalité :

De Mise en œuvre de la méthode des égalités

La plupart des types de référence ne doivent pas surcharger l'opérateur d'égalité, même s'ils surchargent Equals.

De Directives pour l'implémentation d'égaux et de l'opérateur d'égalité (==)

Remplacez la méthode Equals chaque fois que vous implémentez l'opérateur d'égalité (==), et faites en sorte qu'ils fassent la même chose.

Cela signifie seulement que vous devez surcharger Equals chaque fois que vous implémentez l'opérateur d'égalité. Elle ne pas disent que vous devez remplacer l'opérateur d'égalité lorsque vous remplacez Equals.

2voto

Paul Shannon Points 754

Pour les objets complexes qui donneront lieu à des comparaisons spécifiques, il convient d'implémenter IComparable et de définir la comparaison dans les méthodes de comparaison.

Par exemple, nous disposons d'objets "Véhicules" dont la seule différence peut être le numéro d'immatriculation. Nous utilisons cette comparaison pour nous assurer que la valeur attendue renvoyée lors des tests est celle que nous souhaitons.

0 votes

Merci pour ça, Paul. J'ai pris note de l'interface IComparable, même si je pense que dans ce cas précis, elle sera probablement superflue puisque je veux simplement vérifier l'égalité.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X