GetHashCode remplace l’object 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 rendu IEquatable et object.Equals l’object. object.Equals comme ceci:

 public override bool Equals(object obj) { return Equals(obj as SimpleTableRow); } public bool Equals(SimpleTableRow 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); } 

Lorsque vous object.Equals override.Equals, je dois aussi remplacer GetHashCode . Mais quel code dois-je mettre en œuvre? Comment créer un code de hachage à partir d’un tableau générique? Et comment puis-je le combiner avec le nombre entier Id ?

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

En raison des problèmes soulevés dans ce thread, je publie une autre réponse montrant ce qui se passe si vous vous trompez … principalement, que vous ne pouvez pas utiliser GetHashCode() du tableau; Le comportement correct est qu’aucun avertissement n’est imprimé lors de son exécution … 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 first = new SimpleTableRow(1, 2, 3, 4, 5, 6), second = new SimpleTableRow(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> set = new HashSet>(); 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 : IEquatable> { 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); } public bool Equals(SimpleTableRow 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); } } 

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

Étant donné que le hashCode est en quelque sorte une clé pour stocker l’object (similaire à 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.

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

Il y a plusieurs bons points dans les commentaires et autres réponses. L’OP doit déterminer si les valeurs doivent être utilisées dans la “clé” si l’object est utilisé comme clé dans un dictionnaire. Si oui, alors ils devraient faire partie du code de hachage, sinon, pas.

D’un autre côté, je ne sais pas pourquoi la méthode GetHashCode doit refléter SequenceEqual. Il s’agit de calculer un index dans une table de hachage, pour ne pas ê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, il convient alors de choisir un algorithme prenant en compte la séquence. Si la séquence n’a pas vraiment d’importance, gagnez du temps et ne la prenez pas en compte.

Je le ferais de cette façon:

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

Je sais que ce sujet est assez ancien, mais j’ai écrit cette méthode pour me permettre de calculer des codes de hachage de plusieurs objects. Cela a été très utile pour ce cas même. Ce n’est pas parfait, mais il répond à mes besoins et probablement aussi au vôtre.

Je ne peux pas vraiment en prendre le crédit. J’ai eu le concept de certaines implémentations .net gethashcode. J’utilise le 419 (après tout, c’est mon grand prime préféré), mais vous pouvez choisir à peu près n’importe quel nombre 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 args) { if (args == null) return new object().GetHashCode(); unchecked { return args.Aggregate(0, (current, next) => (current*419) ^ (next ?? new object()).GetHashCode()); } } } 

A condition que Id et Values ​​ne changeront jamais et Values ​​n’est pas nul …

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

Notez que votre classe n’est pas immuable, car tout le monde 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.

J’ai juste dû append une autre réponse car l’une des solutions les plus évidentes (et les plus faciles à implémenter) n’était pas mentionnée – n’incluant pas la collection dans votre calcul GetHashCode !

La principale chose qui semblait avoir été oubliée ici est que l’unicité du résultat de GetHashCode n’est pas requirejse (ou dans de nombreux cas même possible). Les objects inégaux n’ont pas à renvoyer des codes de hachage inégaux, la seule exigence est que les objects égaux renvoient des codes de hachage égaux. Donc, par cette définition, l’implémentation suivante de GetHashCode est correcte pour tous les objects (en supposant qu’il existe une implémentation Equals correcte):

 public override int GetHashCode() { return 42; } 

Bien sûr, cela donnerait la pire performance possible dans la recherche par table de hachage, O (n) au lieu de O (1), mais il est toujours fonctionnellement correct.

Dans cette optique, ma recommandation générale lors de l’implémentation de GetHashCode pour un object qui possède un type quelconque de collection est de simplement les ignorer et de calculer GetHashCode uniquement sur la base des autres membres scalaires. Cela fonctionnerait plutôt bien sauf si vous mettez dans une table de hachage un grand nombre d’objects où tous les membres scalaires ont des valeurs identiques, ce qui donne des codes de hachage identiques.

L’ignorance des 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 dissortingbution des valeurs de code de hachage. Rappelez-vous que l’utilisation d’un code de hachage est censée améliorer les performances dans une table de hachage en n’exigeant pas d’appeler N fois Equals , mais nécessitera plutôt d’appeler GetHashCode une seule fois et de rechercher rapidement une table de hachage. Si chaque object possède un tableau interne avec 10 000 éléments qui participent tous au calcul du code de hachage, tous les avantages obtenus par la bonne dissortingbution seront probablement perdus. Il serait préférable d’avoir un code de hachage légèrement moins dissortingbué si sa génération est considérablement moins coûteuse.