web-dev-qa-db-fra.com

Quel est le rôle de GetHashCode dans IEqualityComparer <T> dans .NET?

J'essaie de comprendre le rôle de la méthode GetHashCode de l'interface IEqualityComparer.

L'exemple suivant est tiré de MSDN:

using System;
using System.Collections.Generic;
class Example {
    static void Main() {
        try {

            BoxEqualityComparer boxEqC = new BoxEqualityComparer();

            Dictionary<Box, String> boxes = new Dictionary<Box,
                                                string>(boxEqC);

            Box redBox = new Box(4, 3, 4);
            Box blueBox = new Box(4, 3, 4);

            boxes.Add(redBox, "red");
            boxes.Add(blueBox, "blue");

            Console.WriteLine(redBox.GetHashCode());
            Console.WriteLine(blueBox.GetHashCode());
        }
        catch (ArgumentException argEx) {

            Console.WriteLine(argEx.Message);
        }
    }
}

public class Box {
    public Box(int h, int l, int w) {
        this.Height = h;
        this.Length = l;
        this.Width = w;
    }
    public int Height { get; set; }
    public int Length { get; set; }
    public int Width { get; set; }
}

class BoxEqualityComparer : IEqualityComparer<Box> {

    public bool Equals(Box b1, Box b2) {
        if (b1.Height == b2.Height & b1.Length == b2.Length
                            & b1.Width == b2.Width) {
            return true;
        }
        else {
            return false;
        }
    }

    public int GetHashCode(Box bx) {
        int hCode = bx.Height ^ bx.Length ^ bx.Width;
        return hCode.GetHashCode();
    }
}

L'implémentation de la méthode Equals ne devrait-elle pas être suffisante pour comparer deux objets Box? C'est là que nous indiquons au framework la règle utilisée pour comparer les objets. Pourquoi le GetHashCode est-il nécessaire?

Merci.

Lucian

133
Lucian

Un peu d'histoire d'abord ...

Chaque objet dans .NET a une méthode Equals et une méthode GetHashCode.

La méthode Equals est utilisée pour comparer un objet avec un autre objet - pour voir si les deux objets sont équivalents.

La méthode GetHashCode génère une représentation entière 32 bits de l'objet. Puisqu'il n'y a pas de limite à la quantité d'informations qu'un objet peut contenir, certains codes de hachage sont partagés par plusieurs objets - le code de hachage n'est donc pas nécessairement unique.

Un dictionnaire est une structure de données vraiment cool qui échange une empreinte mémoire plus élevée en échange de coûts (plus ou moins) constants pour les opérations d'ajout/suppression/récupération. C'est un mauvais choix pour itérer. En interne, un dictionnaire contient un tableau de compartiments, où les valeurs peuvent être stockées. Lorsque vous ajoutez une clé et une valeur à un dictionnaire, la méthode GetHashCode est appelée sur la clé. Le code de hachage renvoyé est utilisé pour déterminer l'index du compartiment dans lequel la paire clé/valeur doit être stockée.

Lorsque vous souhaitez accéder à la valeur, vous transmettez à nouveau la clé. La méthode GetHashCode est appelée sur la clé et le compartiment contenant la valeur est localisé.

Lorsqu'un IEqualityComparer est passé dans le constructeur d'un dictionnaire, les méthodes IEqualityComparer.Equals et IEqualityComparer.GetHashCode sont utilisées à la place des méthodes sur les objets Key.

Maintenant, pour expliquer pourquoi les deux méthodes sont nécessaires, considérons cet exemple:

BoxEqualityComparer boxEqC = new BoxEqualityComparer(); 

Dictionary<Box, String> boxes = new Dictionary<Box, string>(boxEqC); 

Box redBox = new Box(100, 100, 25);
Box blueBox = new Box(1000, 1000, 25);

boxes.Add(redBox, "red"); 
boxes.Add(blueBox, "blue"); 

En utilisant la méthode BoxEqualityComparer.GetHashCode dans votre exemple, ces deux boîtes ont le même code de hachage - 100 ^ 100 ^ 25 = 1000 ^ 1000 ^ 25 = 25 - même si elles ne sont clairement pas le même objet. La raison pour laquelle il s'agit du même code de hachage dans ce cas est que vous utilisez l'opérateur ^ (bitwise exclusif-OR), donc 100 ^ 100 annule en laissant zéro, tout comme 1000 ^ 1000. Lorsque deux objets différents ont la même clé, nous appelons cela une collision.

Lorsque nous ajoutons deux paires clé/valeur avec le même code de hachage à un dictionnaire, elles sont toutes les deux stockées dans le même compartiment. Ainsi, lorsque nous voulons récupérer une valeur, la méthode GetHashCode est appelée sur notre clé pour localiser le compartiment. Puisqu'il y a plus d'une valeur dans le compartiment, le dictionnaire itère sur toutes les paires clé/valeur du compartiment appelant la méthode Equals sur les clés pour trouver la bonne.

Dans l'exemple que vous avez publié, les deux cases sont équivalentes, donc la méthode Equals renvoie true. Dans ce cas, le dictionnaire a deux clés identiques, donc il lève une exception.

TLDR

Donc, en résumé, la méthode GetHashCode est utilisée pour générer une adresse où l'objet est stocké. Un dictionnaire n'a donc pas à le rechercher. Il calcule simplement le hashcode et saute à cet emplacement. La méthode Equals est un meilleur test d'égalité, mais ne peut pas être utilisée pour mapper un objet dans un espace d'adressage.

J'espère que ça t'as aidé

196
sheikhjabootie

GetHashCode est utilisé dans les collections du dictionnaire et crée un hachage pour y stocker des objets. Voici un bel article pourquoi et comment utiliser IEqualtyComparer et GetHashCode - http://dotnetperls.com/iequalitycomparer

7
Ash

Bien qu'il soit possible pour un Dictionary<TKey,TValue> pour que son GetValue et des méthodes similaires appellent Equals sur chaque clé stockée pour voir si elle correspond à celle recherchée, ce serait très lent. Au lieu de cela, comme de nombreuses collections basées sur le hachage, il s'appuie sur GetHashCode pour exclure rapidement la plupart des valeurs non correspondantes de la considération. Si l'appel de GetHashCode sur un élément recherché donne 42, et une collection a 53 917 éléments, mais l'appel de GetHashCode sur 53 914 des éléments a donné une valeur autre que 42, alors seulement 3 éléments devront être comparé à ceux recherchés. Les 53 914 autres peuvent être ignorés en toute sécurité.

La raison pour laquelle un GetHashCode est inclus dans un IEqualityComparer<T> est de prévoir la possibilité que le consommateur d'un dictionnaire veuille considérer comme des objets égaux qui normalement pas se considèrent comme égaux. L'exemple le plus courant serait un appelant qui souhaite utiliser des chaînes comme clés mais utiliser des comparaisons insensibles à la casse. Pour que cela fonctionne efficacement, le dictionnaire devra avoir une certaine forme de fonction de hachage qui produira la même valeur pour "Fox" et "FOX", mais avec un peu de chance, quelque chose d'autre pour "box" ou "zebra". Étant donné que la méthode GetHashCode intégrée à String ne fonctionne pas de cette façon, le dictionnaire devra obtenir une telle méthode ailleurs et IEqualityComparer<T> est l'endroit le plus logique car le besoin d'un tel code de hachage serait très fortement associé à une méthode Equals qui considère "Fox" et "FOX" identiques, mais pas à "box" ou "zèbre".

3
supercat