web-dev-qa-db-fra.com

Remplacement GetHashCode de l'objet contenant un tableau générique

J'ai une classe qui contient les deux propriétés suivantes:

public int Id      { get; private set; }
public T[] Values  { get; private set; }

Je l'ai fait IEquatable<T> et a remplacé le object.Equals comme ça:

public override bool Equals(object obj)
{
    return Equals(obj as SimpleTableRow<T>);
}

public bool Equals(SimpleTableRow<T> other)
{
    // Check for null
    if(ReferenceEquals(other, null))
        return false;

    // Check for same reference
    if(ReferenceEquals(this, other))
        return true;

    // Check for same Id and same Values
    return Id == other.Id && Values.SequenceEqual(other.Values);
}

En cas de substitution object.Equals Je dois également remplacer GetHashCode bien sûr. Mais quel code dois-je implémenter? Comment créer un code de hachage à partir d'un tableau générique? Et comment le combiner avec l'entier Id?

public override int GetHashCode()
{
    return // What?
}
54
Svish

En raison des problèmes soulevés dans ce fil, je poste une autre réponse indiquant ce qui se passe si vous vous trompez ... principalement, que vous ne pouvez pas utiliser la fonction GetHashCode() du tableau; le comportement correct est qu'aucun avertissement n'est imprimé lorsque vous l'exécutez ... changez les commentaires pour le corriger:

using System;
using System.Collections.Generic;
using System.Linq;
static class Program
{
    static void Main()
    {
        // first and second are logically equivalent
        SimpleTableRow<int> first = new SimpleTableRow<int>(1, 2, 3, 4, 5, 6),
            second = new SimpleTableRow<int>(1, 2, 3, 4, 5, 6);

        if (first.Equals(second) && first.GetHashCode() != second.GetHashCode())
        { // proven Equals, but GetHashCode() disagrees
            Console.WriteLine("We have a problem");
        }
        HashSet<SimpleTableRow<int>> set = new HashSet<SimpleTableRow<int>>();
        set.Add(first);
        set.Add(second);
        // which confuses anything that uses hash algorithms
        if (set.Count != 1) Console.WriteLine("Yup, very bad indeed");
    }
}
class SimpleTableRow<T> : IEquatable<SimpleTableRow<T>>
{

    public SimpleTableRow(int id, params T[] values) {
        this.Id = id;
        this.Values = values;
    }
    public int Id { get; private set; }
    public T[] Values { get; private set; }

    public override int GetHashCode() // wrong
    {
        return Id.GetHashCode() ^ Values.GetHashCode();
    }
    /*
    public override int GetHashCode() // right
    {
        int hash = Id;
        if (Values != null)
        {
            hash = (hash * 17) + Values.Length;
            foreach (T t in Values)
            {
                hash *= 17;
                if (t != null) hash = hash + t.GetHashCode();
            }
        }
        return hash;
    }
    */
    public override bool Equals(object obj)
    {
        return Equals(obj as SimpleTableRow<T>);
    }
    public bool Equals(SimpleTableRow<T> other)
    {
        // Check for null
        if (ReferenceEquals(other, null))
            return false;

        // Check for same reference
        if (ReferenceEquals(this, other))
            return true;

        // Check for same Id and same Values
        return Id == other.Id && Values.SequenceEqual(other.Values);
    }
}
83
Marc Gravell

FWIW, il est très dangereux d'utiliser le contenu des valeurs dans votre code de hachage. Vous ne devez le faire que si vous pouvez garantir que cela ne changera jamais. Cependant, comme il est exposé, je ne pense pas que cela soit possible. Le code de hachage d'un objet ne doit jamais changer. Sinon, il perd sa valeur de clé dans une table de hachage ou un dictionnaire. Considérez le bug difficile à trouver d'utiliser un objet comme clé dans une table de hachage, son code de hachage change en raison d'une influence extérieure et vous ne pouvez plus le trouver dans la table de hachage!

30
Dustin Campbell

Étant donné que le hashCode est un peu une clé pour stocker l'objet (comme dans une table de hachage), j'utiliserais simplement Id.GetHashCode ()

Que diriez-vous de quelque chose comme:

    public override int GetHashCode()
    {
        int hash = Id;
        if (Values != null)
        {
            hash = (hash * 17) + Values.Length;
            foreach (T t in Values)
            {
                hash *= 17;
                if (t != null) hash = hash + t.GetHashCode();
            }
        }
        return hash;
    }

Cela devrait être compatible avec SequenceEqual, plutôt que de faire une comparaison de référence sur le tableau.

2
Marc Gravell
public override int GetHashCode() {
   return Id.GetHashCode() ^ Values.GetHashCode();  
}

Il y a plusieurs bons points dans les commentaires et autres réponses. L'OP devrait déterminer si les valeurs seraient utilisées dans le cadre de la "clé" si l'objet était utilisé comme clé dans un dictionnaire. Si c'est le cas, alors ils devraient faire partie du code de hachage, sinon, non.

D'un autre côté, je ne sais pas pourquoi la méthode GetHashCode devrait refléter SequenceEqual. Il est destiné à calculer un index dans une table de hachage, et non à être le déterminant complet de l'égalité. S'il existe de nombreuses collisions de tables de hachage utilisant l'algorithme ci-dessus, et si elles diffèrent dans la séquence des valeurs, alors un algorithme doit être choisi qui prend en compte la séquence. Si la séquence n'a pas vraiment d'importance, gagnez du temps et n'en tenez pas compte.

1
John Saunders

J'ai juste dû ajouter une autre réponse parce que l'une des solutions les plus évidentes (et les plus faciles à implémenter) n'était pas mentionnée - sans inclure la collection dans votre calcul GetHashCode!

La principale chose qui semblait avoir oublié ici est que l'unicité du résultat de GetHashCode n'est pas requise (ou dans de nombreux cas, même possible). Les objets inégaux ne doivent pas renvoyer des codes de hachage inégaux, la seule exigence est que des objets égaux renvoient des codes de hachage égaux. Ainsi, selon cette définition, l'implémentation suivante de GetHashCode est correcte pour tous les objets (en supposant qu'il existe une implémentation correcte de Equals):

public override int GetHashCode() 
{ 
    return 42; 
} 

Bien sûr, cela donnerait les pires performances possibles dans la recherche de table de hachage, O(n) au lieu de O (1), mais elle est toujours fonctionnellement correcte.

Dans cet esprit, ma recommandation générale lors de l'implémentation de GetHashCode pour un objet qui se trouve avoir tout type de collection en tant qu'un ou plusieurs de ses membres est de simplement les ignorer et de calculer GetHashCode uniquement sur la base de les autres membres scalaires. Cela fonctionnerait plutôt bien, sauf si vous mettez dans une table de hachage un grand nombre d'objets où tous leurs membres scalaires ont des valeurs identiques, ce qui donne des codes de hachage identiques.

Ignorer les membres de la collection lors du calcul du code de hachage peut également entraîner une amélioration des performances, malgré la diminution de la distribution des valeurs du code de hachage. N'oubliez pas que l'utilisation d'un code de hachage est censée améliorer les performances dans une table de hachage en ne nécessitant pas d'appeler Equals N fois, et ne nécessitera à la place d'appeler GetHashCode qu'une seule fois et une recherche rapide de table de hachage. Si chaque objet a un tableau interne avec 10 000 éléments qui participent tous au calcul du code de hachage, tous les avantages obtenus par la bonne distribution seraient probablement perdus. Il serait préférable d'avoir un code de hachage légèrement moins distribué si sa génération est considérablement moins coûteuse.

1
Allon Guralnek

Je le ferais de cette façon:

long result = Id.GetHashCode();
foreach(T val in Values)
    result ^= val.GetHashCode();
return result;
0
Grzenio

À condition que l'ID et les valeurs ne changent jamais et que les valeurs ne soient pas nulles ...

public override int GetHashCode()
{
  return Id ^ Values.GetHashCode();
}

Notez que votre classe n'est pas immuable, car n'importe qui peut modifier le contenu des valeurs car il s'agit d'un tableau. Compte tenu de cela, je n'essaierais pas de générer un hashcode en utilisant son contenu.

0
Dustin Campbell

Je sais que ce fil est assez ancien, mais j'ai écrit cette méthode pour me permettre de calculer les codes de hachage de plusieurs objets. Cela a été très utile dans ce cas précis. Ce n'est pas parfait, mais il répond à mes besoins et très probablement aux vôtres.

Je ne peux vraiment pas m'en attribuer le mérite. J'ai obtenu le concept de certaines des implémentations de .net gethashcode. J'utilise 419 (après tout, c'est mon grand choix préféré), mais vous pouvez choisir à peu près n'importe quel choix raisonnable (pas trop petit ... pas trop grand).

Alors, voici comment obtenir mes codes de hachage:

using System.Collections.Generic;
using System.Linq;

public static class HashCodeCalculator
{
    public static int CalculateHashCode(params object[] args)
    {
        return args.CalculateHashCode();
    }

    public static int CalculateHashCode(this IEnumerable<object> args)
    {
        if (args == null)
            return new object().GetHashCode();

        unchecked
        {
            return args.Aggregate(0, (current, next) => (current*419) ^ (next ?? new object()).GetHashCode());
        }
    }
}
0
D. Patrick