c# - multiple - Pourquoi est-il important de remplacer GetHashCode lorsque la méthode Equals est surchargée?




gethashcode implementation c# (8)

Étant donné la classe suivante

public class Foo
{
    public int FooId { get; set; }
    public string FooName { get; set; }

    public override bool Equals(object obj)
    {
        Foo fooItem = obj as Foo;

        return fooItem.FooId == this.FooId;
    }

    public override int GetHashCode()
    {
        // Which is preferred?

        return base.GetHashCode();

        //return this.FooId.GetHashCode();
    }
}

J'ai remplacé la méthode Equals car Foo représente une ligne pour la table de Foo . Quelle est la méthode préférée pour redéfinir le GetHashCode ?

Pourquoi est-il important de remplacer GetHashCode ?


C'est parce que le framework nécessite que deux objets identiques aient le même hashcode. Si vous substituez la méthode equals pour effectuer une comparaison spéciale de deux objets et que les deux objets sont considérés comme identiques par la méthode, le code de hachage des deux objets doit également être identique. (Dictionnaires et Hashtables s'appuient sur ce principe).


Ce n'est pas nécessairement important. Cela dépend de la taille de vos collections et de vos exigences de performance et si votre classe sera utilisée dans une bibliothèque où vous ne connaissez pas les exigences de performance. Je sais souvent que mes tailles de collection ne sont pas très grandes et mon temps est plus précieux que quelques microsecondes de performance obtenues en créant un code de hachage parfait; donc (pour se débarrasser de l'avertissement ennuyeux par le compilateur) j'utilise simplement:

   public override int GetHashCode()
   {
      return base.GetHashCode();
   }

(Bien sûr, je pourrais utiliser un #pragma pour désactiver l'avertissement, mais je préfère de cette façon.)

Lorsque vous êtes dans la position où vous avez besoin de la performance que tous les problèmes mentionnés par d'autres ici s'appliquent, bien sûr. Le plus important - sinon vous obtiendrez des résultats erronés lors de la récupération des éléments d'un ensemble de hachage ou d'un dictionnaire: le code de hachage ne doit pas varier avec la durée de vie d'un objet (plus précisément pendant le temps). une clé dans un dictionnaire): par exemple, ce qui suit est faux car Value est public et peut donc être changé en externe à la classe pendant la durée de vie de l'instance, donc vous ne devez pas l'utiliser comme base pour le code de hachage:


   class A
   {
      public int Value;

      public override int GetHashCode()
      {
         return Value.GetHashCode(); //WRONG! Value is not constant during the instance's life time
      }
   }    

D'un autre côté, si la valeur ne peut pas être modifiée, vous pouvez utiliser:


   class A
   {
      public readonly int Value;

      public override int GetHashCode()
      {
         return Value.GetHashCode(); //OK  Value is read-only and can't be changed during the instance's life time
      }
   }


En remplaçant Equals, vous indiquez que vous êtes le mieux placé pour comparer deux instances d'un type donné. Vous êtes donc probablement le meilleur candidat pour fournir le meilleur code de hachage.

Voici un exemple de la façon dont ReSharper écrit une fonction GetHashCode () pour vous:

public override int GetHashCode()
{
    unchecked
    {
        var result = 0;
        result = (result * 397) ^ m_someVar1;
        result = (result * 397) ^ m_someVar2;
        result = (result * 397) ^ m_someVar3;
        result = (result * 397) ^ m_someVar4;
        return result;
    }
}

Comme vous pouvez le voir, il essaie juste de deviner un bon code de hachage basé sur tous les champs de la classe, mais comme vous connaissez le domaine ou les plages de valeurs de votre objet, vous pouvez toujours en fournir un meilleur.


Il est en fait très difficile d'implémenter GetHashCode() correctement car, en plus des règles déjà mentionnées par Marc, le code de hachage ne devrait pas changer pendant la durée de vie d'un objet. Par conséquent, les champs utilisés pour calculer le code de hachage doivent être immuables.

J'ai finalement trouvé une solution à ce problème quand je travaillais avec NHibernate. Mon approche consiste à calculer le code de hachage à partir de l'ID de l'objet. L'ID ne peut être défini que par le constructeur, donc si vous voulez changer l'ID, ce qui est très improbable, vous devez créer un nouvel objet qui a un nouvel ID et donc un nouveau code de hachage. Cette approche fonctionne mieux avec GUID parce que vous pouvez fournir un constructeur sans paramètre qui génère aléatoirement un ID.


Juste pour ajouter sur les réponses ci-dessus:

Si vous ne remplacez pas Equals, le comportement par défaut est que les références des objets sont comparées. La même chose s'applique à hashcode - l'implmentation par défaut est généralement basée sur une adresse mémoire de la référence. Parce que vous avez remplacé Equals, cela signifie que le comportement correct est de comparer tout ce que vous avez implémenté sur Equals et non les références, donc vous devriez faire la même chose pour le hashcode.

Les clients de votre classe s'attendront à ce que le hashcode ait une logique similaire à la méthode equals, par exemple les méthodes linq qui utilisent un IEqualityComparer comparent d'abord les hashcodes et seulement si elles sont égales elles compareront la méthode Equals () qui pourrait être plus chère pour exécuter, si nous n'avons pas implémenté le hashcode, l'objet égal aura probablement des hashcodes différents (parce qu'ils ont une adresse mémoire différente) et sera déterminé à tort comme non égal (Equals () ne frappera même pas).

De plus, excepté le problème que vous pourriez ne pas trouver votre objet si vous l'avez utilisé dans un dictionnaire (parce qu'il a été inséré par un hashcode et quand vous le cherchez le hashcode par défaut sera probablement différent et encore l'égal () ne sera même pas appelé, comme Marc Gravell l'explique dans sa réponse, vous introduisez également une violation du dictionnaire ou du concept hashset qui ne devrait pas permettre des clés identiques - vous avez déjà déclaré que ces objets sont essentiellement les mêmes lorsque vous surclastiez Equals ne pas vouloir les deux comme des clés différentes sur une structure de données supposée avoir une clé unique, mais comme ils ont un hashcode différent, la même clé sera insérée en tant que clé différente.


Le code de hachage est utilisé pour des collections basées sur le hachage comme Dictionary, Hashtable, HashSet, etc. Le but de ce code est de pré-trier très rapidement un objet spécifique en le plaçant dans un groupe spécifique (bucket). Ce pré-tri aide énormément à trouver cet objet lorsque vous avez besoin de le récupérer depuis la collection hash car le code doit rechercher votre objet dans un seul compartiment au lieu de tous les objets qu'il contient. La meilleure distribution des codes de hachage (meilleure unicité) la récupération plus rapide. Dans une situation idéale où chaque objet possède un code de hachage unique, trouver qu'il s'agit d'une opération O (1). Dans la plupart des cas, il approche O (1).


Oui, il est important que votre élément soit utilisé comme clé dans un dictionnaire, ou HashSet<T> , etc. - car il est utilisé (en l'absence d'un IEqualityComparer<T> ) pour regrouper les éléments dans des compartiments. Si le code de hachage de deux éléments ne correspond pas, ils ne peuvent jamais être considérés égaux (les égalités ne seront jamais appelées).

La méthode GetHashCode() doit refléter la logique Equals ; les règles sont:

  • si deux choses sont égales ( Equals(...) == true ) alors elles doivent retourner la même valeur pour GetHashCode()
  • si le GetHashCode() est égal, il n'est pas nécessaire qu'ils soient identiques; c'est une collision, et Equals sera appelé pour voir s'il s'agit d'une réelle égalité ou non.

Dans ce cas, il semble que " return FooId; " est une implémentation de GetHashCode() appropriée. Si vous testez plusieurs propriétés, il est courant de les combiner en utilisant le code ci-dessous, afin de réduire les collisions en diagonale (ie que le new Foo(3,5) ait un code de hachage différent du new Foo(5,3) ):

int hash = 13;
hash = (hash * 7) + field1.GetHashCode();
hash = (hash * 7) + field2.GetHashCode();
...
return hash;

Oh - pour des raisons de commodité, vous pourriez également envisager de fournir des opérateurs == et != Lors de la substitution d' Equals et de GetHashCode .

Une démonstration de ce qui se passe quand vous avez tort est here .


Que diriez-vous:

public override int GetHashCode()
{
    return string.Format("{0}_{1}_{2}", prop1, prop2, prop3).GetHashCode();
}

En supposant que la performance n'est pas un problème :)





hashcode