200 votes

Implémentation par défaut de Object.GetHashCode()

Comment l'implémentation par défaut de GetHashCode() travail ? Et gère-t-il les structures, les classes, les tableaux, etc. de manière suffisamment efficace et performante ?

J'essaie de décider dans quels cas je dois faire mon propre paquetage et dans quels cas je peux me fier à l'implémentation par défaut pour bien faire. Je ne veux pas réinventer la roue, si c'est possible.

0 votes

Jetez un coup d'œil au commentaire que j'ai laissé sur l'article : stackoverflow.com/questions/763731/gethashcode-extension-method

0 votes

44 votes

D'ailleurs, vous pouvez obtenir le hashcode par défaut (même lorsque GetHashCode() a été remplacée) en utilisant System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(o‌​bj)

109voto

Marc Gravell Points 482669

Pour une classe, les valeurs par défaut sont essentiellement l'égalité des références, et cela convient généralement. Si vous écrivez une structure, il est plus courant de remplacer l'égalité (notamment pour éviter la mise en boîte), mais il est très rare que vous écriviez une structure de toute façon !

Lorsque vous outrepassez l'égalité, vous devez toujours avoir un équivalent Equals() et GetHashCode() (c'est-à-dire que pour deux valeurs, si Equals() retourne vrai s'ils debe retournent le même code de hachage, mais l'inverse est no nécessaire) - et il est courant de fournir également des == / != opérateurs, et souvent pour mettre en œuvre IEquatable<T> aussi.

Pour générer le code de hachage, il est courant d'utiliser une somme factorisée, car cela évite les collisions sur les valeurs appariées - par exemple, pour un hachage de base à 2 champs :

unchecked // disable overflow, for the unlikely possibility that you
{         // are compiling with overflow-checking enabled
    int hash = 27;
    hash = (13 * hash) + field1.GetHashCode();
    hash = (13 * hash) + field2.GetHashCode();
    return hash;
}

Cela présente l'avantage de :

  • le hash de {1,2} n'est pas le même que le hash de {2,1}
  • Le hachage de {1,1} n'est pas le même que celui de {2,2}.

etc - ce qui peut être courant si l'on utilise simplement une somme non pondérée, ou xor ( ^ ), etc.

1 votes

Excellente remarque sur l'avantage d'un algorithme à somme pondérée ; je n'en avais pas encore pris conscience !

0 votes

La somme factorisée (telle qu'elle est écrite ci-dessus) ne va-t-elle pas occasionnellement provoquer des exceptions de dépassement de capacité ?

6 votes

@sinelaw oui, il devrait être effectué unchecked . Heureusement, unchecked est la valeur par défaut en C#, mais il serait préférable de le rendre explicite ; édité

96voto

David Brown Points 14365
namespace System {
    public class Object {
        [MethodImpl(MethodImplOptions.InternalCall)]
        internal static extern int InternalGetHashCode(object obj);

        public virtual int GetHashCode() {
            return InternalGetHashCode(this);
        }
    }
}

InternalGetHashCode est mis en correspondance avec un ObjectNative::GetHashCode dans le CLR, qui ressemble à ceci :

FCIMPL1(INT32, ObjectNative::GetHashCode, Object* obj) {  
    CONTRACTL  
    {  
        THROWS;  
        DISABLED(GC_NOTRIGGER);  
        INJECT_FAULT(FCThrow(kOutOfMemoryException););  
        MODE_COOPERATIVE;  
        SO_TOLERANT;  
    }  
    CONTRACTL_END;  

    VALIDATEOBJECTREF(obj);  

    DWORD idx = 0;  

    if (obj == 0)  
        return 0;  

    OBJECTREF objRef(obj);  

    HELPER_METHOD_FRAME_BEGIN_RET_1(objRef);        // Set up a frame  

    idx = GetHashCodeEx(OBJECTREFToObject(objRef));  

    HELPER_METHOD_FRAME_END();  

    return idx;  
}  
FCIMPLEND

La mise en œuvre complète de GetHashCodeEx est assez large, il est donc plus facile de faire un lien vers le code source C++ .

5 votes

Cette citation de la documentation doit provenir d'une version très ancienne. Elle n'est plus écrite de la sorte dans les articles MSDN actuels, probablement parce qu'elle est tout à fait fausse.

4 votes

Ils ont changé la formulation, oui, mais il dit toujours fondamentalement la même chose : "Par conséquent, l'implémentation par défaut de cette méthode ne doit pas être utilisée comme un identifiant d'objet unique à des fins de hachage."

9 votes

Pourquoi la documentation affirme-t-elle que l'implémentation n'est pas particulièrement utile pour le hachage ? Si un objet est égal à lui-même et à rien d'autre, toute méthode de hachage qui renverra toujours la même valeur pour une instance d'objet donnée, et qui renverra généralement des valeurs différentes pour des instances différentes, quel est le problème ?

7voto

Guffa Points 308133

La documentation relative à la GetHashCode méthode pour Objet dit "l'implémentation par défaut de cette méthode ne doit pas être utilisée comme identifiant unique d'objet à des fins de hachage." et celle pour Type de valeur dit "Si vous appelez la méthode GetHashCode du type dérivé, la valeur de retour n'est probablement pas adaptée à une utilisation en tant que clé dans une table de hachage." .

Les types de données de base comme byte , short , int , long , char et string implémenter une bonne méthode GetHashCode. D'autres classes et structures, comme Point par exemple, mettre en œuvre un GetHashCode méthode qui peut ou non convenir à vos besoins spécifiques. Il vous suffit de l'essayer pour voir si elle est suffisante.

La documentation de chaque classe ou structure peut vous indiquer si elle remplace ou non l'implémentation par défaut. Si elle ne le fait pas, vous devez utiliser votre propre implémentation. Pour toutes les classes ou structures que vous créez vous-même et pour lesquelles vous devez utiliser l'attribut GetHashCode vous devez créer votre propre implémentation qui utilise les membres appropriés pour calculer le code de hachage.

2 votes

Je ne suis pas d'accord pour dire que vous devriez régulièrement ajouter votre propre mise en œuvre. Simplement, la grande majorité des classes (en particulier) ne seront jamais testées pour l'égalité - ou lorsqu'elles le sont, l'égalité de référence intégrée est parfaite. Dans l'occasion (déjà rare) d'écrire un struct, ce serait plus commun, vrai.

0 votes

@Marc Gravel : Ce n'est bien sûr pas ce que je voulais dire. Je vais ajuster le dernier paragraphe :)

0 votes

Les types de données de base n'implémentent pas une bonne méthode GetHashCode, du moins dans mon cas. Par exemple, GetHashCode pour int renvoie le nombre lui-même : (123).GetHashCode() renvoie 123.

2voto

Bennett Dill Points 1552

En règle générale, si vous surchargez Equals, vous voulez surcharger GetHashCode. La raison en est que les deux sont utilisés pour comparer l'égalité de votre classe/structure.

Equals est utilisé pour vérifier Foo A, B ;

si (A == B)

Puisque nous savons que le pointeur n'est pas susceptible de correspondre, nous pouvons comparer les membres internes.

Equals(obj o)
{
    if (o == null) return false;
    MyType Foo = o as MyType;
    if (Foo == null) return false;
    if (Foo.Prop1 != this.Prop1) return false;

    return Foo.Prop2 == this.Prop2;
}

GetHashCode est généralement utilisé par les tables de hachage. Le hashcode généré par votre classe devrait toujours être le même pour un état donné de la classe.

Je le fais généralement,

GetHashCode()
{
    int HashCode = this.GetType().ToString().GetHashCode();
    HashCode ^= this.Prop1.GetHashCode();
    etc.

    return HashCode;
}

Certains diront que le hashcode ne devrait être calculé qu'une fois par durée de vie de l'objet, mais je ne suis pas d'accord avec cela (et je me trompe probablement).

En utilisant l'implémentation par défaut fournie par object, à moins que vous ayez la même référence à l'une de vos classes, elles ne seront pas égales entre elles. En surchargeant Equals et GetHashCode, vous pouvez signaler l'égalité en vous basant sur des valeurs internes plutôt que sur la référence des objets.

2 votes

L'approche ^= n'est pas une approche particulièrement bonne pour générer un hachage - elle a tendance à conduire à un grand nombre de collisions courantes/prévisibles - par exemple si Prop1 = Prop2 = 3.

0 votes

Si les valeurs sont les mêmes, je ne vois pas de problème de collision puisque les objets sont égaux. Le 13 * Hash + NewHash semble intéressant cependant.

3 votes

Ben : essayez-le pour Obj1 { Prop1 = 12, Prop2 = 12 } et Obj

1voto

Daniel Marshall Points 11

Si vous ne vous occupez que des POCO, vous pouvez utiliser cet utilitaire pour vous simplifier quelque peu la vie :

var hash = HashCodeUtil.GetHashCode(
           poco.Field1,
           poco.Field2,
           ...,
           poco.FieldN);

...

public static class HashCodeUtil
{
    public static int GetHashCode(params object[] objects)
    {
        int hash = 13;

        foreach (var obj in objects)
        {
            hash = (hash * 7) + (!ReferenceEquals(null, obj) ? obj.GetHashCode() : 0);
        }

        return hash;
    }
}

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