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


É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é le Equals méthode parce que Foo représenter une rangée pour le Foos table. Quelle est la méthode préférée pour passer outre la GetHashCode?

Pourquoi est-il important de remplacer GetHashCode?


1164
2017-12-16 13:41


origine


Réponses:


Oui, il est important que votre article soit utilisé comme clé dans un dictionnaire, ou HashSet<T>, etc - puisque cela est utilisé (en l'absence d'une coutume IEqualityComparer<T>) pour regrouper les articles dans des seaux. Si le code de hachage de deux éléments ne correspond pas, ils peuvent jamais être considéré comme égal (Equals ne sera jamais appelé).

le GetHashCode() méthode devrait refléter le Equals logique; les règles sont:

  • si deux choses sont égales (Equals(...) == true) puis ils doit retourner la même valeur pour GetHashCode()
  • si la GetHashCode() est égal, il est ne pas nécessaire pour eux d'être les mêmes; c'est une collision, et Equals sera appelé pour voir si c'est une véritable égalité ou non.

Dans ce cas, cela ressemble à "return FooId;"est un approprié GetHashCode() la mise en oeuvre. 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 (c'est-à-dire de sorte que new Foo(3,5) a un code de hachage différent de new Foo(5,3)):

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

Oh - pour plus de commodité, vous pourriez également envisager de fournir == et != opérateurs en cas de dépassement Equals et GetHashCode.


Une démonstration de ce qui se passe quand vous vous trompez est ici.


1097
2017-12-16 13:47



C'est en fait très difficile à mettre en œuvre 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.


114
2017-12-21 12:39



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.


41
2017-12-16 13:48



S'il vous plaît ne pas oublier de vérifier le paramètre obj contre null en cas de dépassement Equals(). Et comparez également le type.

public override bool Equals(object obj)
{
    if (obj == null || GetType() != obj.GetType())
        return false;

    Foo fooItem = obj as Foo;

    return fooItem.FooId == this.FooId;
}

La raison en est: Equals doit retourner faux sur la comparaison à null. Voir également http://msdn.microsoft.com/en-us/library/bsc2ak47.aspx


32
2017-11-17 07:46



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 :)


23
2017-11-25 00:48



C'est parce que le framework nécessite que deux objets identiques aient le même hashcode. Si vous remplacez 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).


9
2017-12-16 13:48



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 de 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 remplacez Equals, donc vous ne voulez pas qu'ils soient tous deux des clés différentes sur une structure de données supposée avoir une clé unique. Mais parce qu'ils ont un hashcode différent, la "même" clé sera insérée en tant que différente.


8
2017-11-12 13:48



Nous avons deux problèmes à gérer.

  1. Vous ne pouvez pas fournir un sensible GetHashCode() si un champ dans le l'objet peut être changé. Aussi souvent un objet ne sera JAMAIS utilisé dans un collection qui dépend de GetHashCode(). Donc, le coût de exécution GetHashCode() ne vaut souvent pas la peine, ou ce n'est pas possible.

  2. Si quelqu'un met votre objet dans une collection qui appelle GetHashCode() et vous avez outrepassé Equals() sans faire aussi GetHashCode() se comporter de manière correcte, cette personne peut passer des jours traquer le problème.

Par conséquent, je le fais par défaut.

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()
    {
        // Some comment to explain if there is a real problem with providing GetHashCode() 
        // or if I just don't see a need for it for the given class
        throw new Exception("Sorry I don't know what GetHashCode should do for this class");
    }
}

7
2017-11-19 10:17



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).


5
2018-02-21 11:36



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 que vous faire 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 d'é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 où le code de hachage est nécessaire, comme étant une clé dans un dictionnaire): par exemple, ce qui suit est erroné car la valeur est publique et peut donc être modifiée à l'extérieur de la classe pendant la durée de vie. l'instance, vous ne devez donc 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
      }
   }


3
2018-06-26 23:21



Je crois comprendre que l'original GetHashCode () renvoie l'adresse mémoire de l'objet, il est donc essentiel de le remplacer si vous souhaitez comparer deux objets différents.

ÉDITÉ: Cela était incorrect, la méthode GetHashCode () d'origine ne peut pas assurer l'égalité de 2 valeurs. Bien que les objets qui sont égaux retournent le même code de hachage.


0
2017-10-07 17:06