Pourquoi est-il important de surcharger GetHashCode quand la méthode Equals est annulée?

compte tenu de 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 parce que Foo représente une rangée pour la table Foo S. Quelle est la méthode privilégiée pour annuler le GetHashCode ?

Pourquoi est-il important de remplacer GetHashCode ?

1200
demandé sur Irshad 2008-12-16 16:41:18

12 réponses

Oui, il est important si votre article sera utilisé comme une clé dans un dictionnaire, ou HashSet<T> , etc - car il est utilisé (en l'absence d'une coutume IEqualityComparer<T> ) pour grouper des articles dans des seaux. Si le code de hachage pour deux articles ne correspond pas, ils peuvent jamais être considérés comme égaux ( Equals ne sera tout simplement jamais appelé).

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

  • si deux choses sont égales ( Equals(...) == true ) alors ils doit retourner la même valeur pour GetHashCode()
  • si le GetHashCode() est égal, c'est pas nécessaire pour eux d'être le même; c'est une collision, et Equals sera appelé pour voir si c'est une égalité réelle ou pas.

dans ce cas, il ressemble à" return FooId; " est une mise en œuvre appropriée GetHashCode() . Si vous testez plusieurs propriétés, il est courant de les combiner en utilisant le code comme ci-dessous, pour réduire les collisions diagonales (i.e. 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 des raisons de commodité, vous pourriez également envisager de fournir des opérateurs == et != lorsque vous remplacez Equals et GetHashCode .


Une démonstration de ce qui se passe quand vous obtenez ce le mal est ici .

1127
répondu Marc Gravell 2017-05-23 11:47:29

il est en fait très difficile de mettre en œuvre correctement GetHashCode() car, en plus des règles déjà mentionnées par Marc, le code de hachage ne doit pas changer pendant la durée de vie d'un objet. Par conséquent, les champs qui sont utilisés pour calculer le code de hachage doit être immuable.

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 peu probable, vous devez créer un nouvel objet qui a une nouvelle ID, et donc, un nouveau code de hachage. Cette approche fonctionne mieux avec les GUIDs parce que vous pouvez fournir un constructeur sans paramètres qui génère au hasard un ID.

123
répondu Albic 2010-05-11 18:55:11

en surpassant des égaux vous déclarez fondamentalement que vous êtes celui qui sait mieux comment comparer deux instances d'un type donné, donc vous êtes susceptible d'être le meilleur candidat pour fournir le meilleur code de hachage.

ceci est 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 puisque vous connaissez le domaine de votre objet ou fourchettes de valeur vous pouvez toujours en fournir une meilleure.

43
répondu Trap 2018-02-02 10:21:35

s'il vous Plaît n'oubliez pas de vérifier le paramètre obj contre null lors de la substitution Equals() . Et aussi comparer 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 la suivante: Equals doit retourner false par rapport à null . Voir aussi http://msdn.microsoft.com/en-us/library/bsc2ak47.aspx

32
répondu huha 2013-04-03 20:14:13

c'est parce que le framework exige que deux objets qui sont les mêmes doivent avoir le même hashcode. Si vous outrepassez la méthode des égaux pour faire une comparaison spéciale de deux objets et que les deux objets sont considérés comme identiques par la méthode, alors le code de hachage des deux objets doit aussi être le même. (Les dictionnaires et les Hashtables s'appuient sur ce principe).

9
répondu kemiller2002 2008-12-16 20:36:40

juste pour ajouter les réponses ci-dessus:

si vous n'outrepassez pas Equal, alors le comportement par défaut est que les références des objets sont comparées. La même chose s'applique au hashcode - l'implementation par défaut est typiquement basée sur une adresse mémoire de la référence. Parce que vous avez outrepassé Equals cela signifie que le comportement correct est de comparer ce que vous avez implémenté sur Equals et pas 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 à celle de la méthode equals, par exemple les méthodes linq qui utilisent un IEqualityComparer comparent d'abord les hashcodes et seulement s'ils sont égaux ils compareront la méthode Equals() qui pourrait être plus coûteuse à exécuter, si nous n'implémentons pas le hashcode, l'objet equal aura probablement des hashcodes différents (parce qu'ils ont une adresse mémoire différente) et sera déterminé à tort comme non equal (Equals() ne sera même pas activé).

de plus, sauf le problème que vous pourriez ne pas être en mesure de 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 les égaux () ne sera même pas appelé, comme Marc Gravell 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 overrode égale de sorte que vous ne voulez pas les deux comme des clés différentes sur une structure de données qui suppose d'avoir une clé unique. Mais parce qu'ils ont un hashcode différent la "même" clé sera insérée comme différente.

8
répondu BornToCode 2016-09-07 10:24:28

Nous avons deux problèmes à affronter.

  1. Vous ne pouvez pas fournir une sensible GetHashCode() si n'importe quel champ dans la l'objet peut être modifié. Souvent aussi, un objet ne sera jamais utilisé dans un collection qui dépend de GetHashCode() . De sorte que le coût de la mise en œuvre de GetHashCode() n'en vaut souvent pas la peine, possible.

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

donc par défaut je le fais.

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
répondu Ian Ringrose 2015-08-17 20:43:52

le code Hash est utilisé pour les collections basées sur le hash comme le dictionnaire, le Hashtable, le HashSet etc. Le but de ce code est de pré-trier très rapidement un objet spécifique en le mettant dans un groupe spécifique (seau). Ce pré-tri aide énormément à trouver cet objet quand vous avez besoin de le récupérer à partir de la collection de hachage parce que le code doit rechercher votre objet dans un seul seau au lieu de tous les objets qu'il contient. La meilleure distribution des codes de hachage (meilleure unicité) récupération. Dans la situation idéale où chaque objet a un code de hachage unique, le trouver est une opération O(1). Dans la plupart des cas, il s'approche de O(1).

5
répondu Maciej D. 2012-02-21 11:36:55

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 peut-être pas les exigences de performance. Je sais souvent que mes tailles de collection ne sont pas très grandes et que mon temps est plus précieux que quelques microsecondes de performance gagnées 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 de toutes les questions mentionnées par d'autres ici s'appliquent, bien sûr. le plus important - sinon vous obtiendrez de mauvais résultats lors de la récupération des articles d'un jeu de hash ou un dictionnaire: le code de hash ne doit pas varier avec le temps de vie d'un objet (plus précisément, pendant le temps où le code de hachage est nécessaire, par exemple pendant que vous êtes une clé dans un dictionnaire): par exemple, ce qui suit est erroné car la valeur est publique et peut donc être changée extérieurement à 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 changée c'est ok d'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
      }
   }

4
répondu ILoveFortran 2011-06-26 23:21:09

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

édité: C'était incorrect, la méthode originale de GetHashCode() ne peut pas assurer l'égalité de 2 valeurs. Bien que les objets qui sont égaux renvoient le même code de hachage.

0
répondu user2855602 2013-12-09 19:35:53

ci-dessous l'utilisation du reflet me semble une meilleure option considérant les propriétés publiques comme avec cela vous n'avez pas à vous soucier de l'Ajout / Suppression de propriétés (bien que pas si scénario commun). J'ai trouvé que ça marchait mieux aussi.(Temps comparé à L'aide de la Diagonistique de chronomètre).

    public int getHashCode()
    {
        PropertyInfo[] theProperties = this.GetType().GetProperties();
        int hash = 31;
        foreach (PropertyInfo info in theProperties)
        {
            if (info != null)
            {
                var value = info.GetValue(this,null);
                if(value != null)
                unchecked
                {
                    hash = 29 * hash ^ value.GetHashCode();
                }
            }
        }
        return hash;  
    }
-5
répondu Guanxi 2014-03-25 18:49:18