2010-12-12 2 views
10

Quelle est votre approche pour écrire des vérifications d'égalité pour les structs et classes que vous créez?Vérification d'égalité C#

1) Est-ce que la "pleine" égalité de vérification exigent que beaucoup de code passe-partout (comme override Equals, override GetHashCode, Equals générique, operator==, operator!=)?

2) Spécifiez-vous explicitement que vos classes modélisent l'interface IEquatable<T>?

3) Est-ce que je comprends bien, qu'il n'y a aucun moyen réel pour appliquer automatiquement Equals l'emporte, quand j'invoquer quelque chose comme a == b et je dois toujours mettre en œuvre à la fois les membres Equals et operator==?

Répondre

20

vous avez raison, c'est beaucoup de code-plaque de la chaudière, et vous devez tout mettre en œuvre séparément.

Je voudrais recommander:

  • Si vous allez mettre en œuvre l'égalité de valeur du tout, passer outre GetHashCode et Equals(object) - création pour == et surcharges mise en œuvre IEquatable<T> sans faire qui pourrait entraîner un comportement très inattendu
  • je toujours mettre en œuvre IEquatable<T> si vous êtes remplaçant Equals(object) et GetHashCode
  • Je ne surcharger l'opérateur == plus rarement
  • la mise en œuvre correctement l'égalité pour les classes non scellées est difficile, et peut encore produire des résultats surprenants/indésirables. Si vous avez besoin d'égalité pour les types dans une hiérarchie, mettre en œuvre IEqualityComparer<T> exprimant la comparaison qui vous intéresse.
  • l'égalité pour les types mutables est généralement une mauvaise idée, comme deux objets peuvent être égaux, puis plus tard inégale ...Si un objet est muté (d'une manière affectant l'égalité) après qu'il a été utilisé comme clé dans une table de hachage, vous ne pourrez pas le retrouver.
  • Une partie de la plaque de la chaudière est légèrement différente pour les structures ... mais comme Marc, j'écris très rarement mes propres structs.

Voici un exemple d'implémentation:

using System; 

public sealed class Foo : IEquatable<Foo> 
{ 
    private readonly string name; 
    public string Name { get { return name; } } 

    private readonly int value; 
    public int Value { get { return value; } } 

    public Foo(string name, int value) 
    { 
     this.name = name; 
     this.value = value; 
    } 

    public override bool Equals(object other) 
    { 
     return Equals(other as Foo); 
    } 

    public override int GetHashCode() 
    { 
     int hash = 17; 
     hash = hash * 31 + (name == null ? 0 : name.GetHashCode()); 
     hash = hash * 31 + value; 
     return hash; 
    } 

    public bool Equals(Foo other) 
    { 
     if ((object) other == null) 
     { 
      return false; 
     } 
     return name == other.name && value == other.value; 
    } 

    public static bool operator ==(Foo left, Foo right) 
    { 
     return object.Equals(left, right); 
    } 

    public static bool operator !=(Foo left, Foo right) 
    { 
     return !(left == right); 
    } 
} 

Et oui, c'est un diable de beaucoup de passe-partout, très peu de ce qui change entre les implémentations :(

La mise en œuvre de == est-légèrement moins efficace que cela pourrait être, car il appellera à Equals(object) qui doit faire le contrôle de type dynamique ... mais l'alternative est encore plus chaudière-plaque, comme ceci:

public static bool operator ==(Foo left, Foo right) 
{ 
    if ((object) left == (object) right) 
    { 
     return true; 
    } 

    // "right" being null is covered in left.Equals(right) 
    if ((object) left == null) 
    { 
     return false; 
    } 
    return left.Equals(right); 
} 
+1

^^ Votre chaque poste est un chapitre d'apprentissage C# .. :) – Dienekes

+0

2 suggestions mineures pour le second bloc de code: 1) Ne devriez-vous pas déplacer '(objet) left == (object) right' de' == 'vers 'Equals' générique? Donc, cela donne la vitesse (bien sûr cela dépend, mais en supposant le pire des cas) de vérifier l'égalité de référence même pour la méthode générique «Equals»? 2) vous n'avez pas besoin de la seconde null vérifier '' (objet) right == null' dans '==' comme vous le faites essentiellement dans 'Equals' générique. Voir mon article .. – nawfal

+0

@nawfal: Je ne pense pas qu'il y ait grand intérêt à le faire dans le cas générique 'Equals' - ça va quand même être rapide dans les cas où c'est * vrai *, et pour les cas où c'est le cas * n'est pas * vrai, c'est ajouter un contrôle supplémentaire pour aucun avantage. Comme pour la partie nulle - cela nécessiterait de nouveau la vérification du type dynamique. Oui, vous pourriez argumenter pour les deux - mais je suis assez content de ce que j'ai écrit il y a deux ans ... –

1

Vous avez juste besoin d'implémenter l'opérateur == pour a == b.
Comme j'aime mes données dans les dictionnaires, je substitue parfois GetHashCode.
Ensuite, j'implémente Equals (comme un standard non mentionné ... c'est parce qu'il n'y a pas de contrainte pour l'égalité lors de l'utilisation de génériques) et spécifiez implémenter IEquatable. Puisque je vais faire cela, je pourrais aussi bien pointer mes implémentations == et! = Sur Equals. :)

6

Je fais rarement quelque chose de spécial pour les cours; pour la plupart des objets normaux, l'égalité référentielle fonctionne bien.

Je écris encore plus rarement un struct; mais puisque les structures représentent les valeurs il est habituellement approprié de fournir l'égalité etc. Ceci impliquerait habituellement tout; Equals, ==,! = Et IEquatable<T> (car cela évite la boxe dans les scénarios en utilisant EqualityComparer<T>.Default.

Le passe-partout est généralement pas trop de problèmes, mais les outils IIRC comme ReSharper peut aider ici.

Oui, il est conseillé de garder Equals et == en synchronisation, et ce qui doit être fait explicitement.

Questions connexes