Comme je n'ai pas trouvé de réponse expliquant pourquoi nous devrions remplacer GetHashCode
et Equals
pour les structures personnalisées et pourquoi l'implémentation par défaut "n'est pas susceptible d'être utilisée comme clé dans une table de hachage", je vais laisser un lien vers ce blog post , ce qui explique pourquoi avec un exemple concret d'un problème survenu.
Je recommande de lire l'intégralité du message, mais voici un résumé (soulignement et clarifications ajoutés).
Raison pour laquelle le hachage par défaut pour les structures est lent et pas très bon:
La façon dont le CLR est conçu, chaque appel à un membre défini dans System.ValueType
ou System.Enum
types [peut] provoquer une allocation de boxe [...]
Un réalisateur d'une fonction de hachage est confronté à un dilemme: faire une bonne distribution de la fonction de hachage ou la rendre rapide. Dans certains cas, il est possible de les atteindre tous les deux, mais il est difficile de le faire de manière générique dansValueType.GetHashCode
.
La fonction de hachage canonique d'une structure "combine" les codes de hachage de tous les champs. Mais le seul moyen d'obtenir un code de hachage d'un champ dans une ValueType
méthode est d' utiliser la réflexion . Ainsi, les auteurs du CLR ont décidé d'échanger de la vitesse sur la distribution et la GetHashCode
version par défaut renvoie simplement un code de hachage d'un premier champ non nul et le «munit» d'un identifiant de type [...] C'est un comportement raisonnable sauf si ce n'est pas le cas . Par exemple, si vous êtes assez malchanceux et que le premier champ de votre structure a la même valeur pour la plupart des instances, alors une fonction de hachage fournira le même résultat tout le temps. Et, comme vous pouvez l'imaginer, cela aura un impact considérable sur les performances si ces instances sont stockées dans un jeu de hachage ou une table de hachage.
[...] La mise en œuvre basée sur la réflexion est lente . Très lent.
[...] Les deux ValueType.Equals
et ValueType.GetHashCode
ont une optimisation spéciale. Si un type n'a pas de "pointeurs" et est correctement compressé [...] alors des versions plus optimales sont utilisées: GetHashCode
itère sur une instance et XORs blocs de 4 octets et la Equals
méthode compare deux instances en utilisant memcmp
. [...] Mais l'optimisation est très délicate. Premièrement, il est difficile de savoir quand l'optimisation est activée [...] Deuxièmement, une comparaison de mémoire ne vous donnera pas forcément les bons résultats . Voici un exemple simple: [...] -0.0
et +0.0
sont égaux mais ont des représentations binaires différentes.
Problème du monde réel décrit dans l'article:
private readonly HashSet<(ErrorLocation, int)> _locationsWithHitCount;
readonly struct ErrorLocation
{
// Empty almost all the time
public string OptionalDescription { get; }
public string Path { get; }
public int Position { get; }
}
Nous avons utilisé un tuple contenant une structure personnalisée avec une implémentation d'égalité par défaut. Et malheureusement, la structure avait un premier champ facultatif qui était presque toujours égal à [chaîne vide] . Les performances étaient correctes jusqu'à ce que le nombre d'éléments de l'ensemble augmente de manière significative, provoquant un réel problème de performances, prenant quelques minutes pour initialiser une collection avec des dizaines de milliers d'éléments.
Donc, pour répondre à la question "dans quels cas je devrais emballer le mien et dans quels cas je peux compter en toute sécurité sur l'implémentation par défaut", au moins dans le cas des structures , vous devez surcharger Equals
et GetHashCode
chaque fois que votre structure personnalisée peut être utilisée comme un entrez une table de hachage ou Dictionary
.
Je recommanderais également la mise IEquatable<T>
en œuvre dans ce cas, pour éviter la boxe.
Comme le disent les autres réponses, si vous écrivez une classe , le hachage par défaut utilisant l'égalité de référence est généralement correct, donc je ne me dérangerais pas dans ce cas, à moins que vous n'ayez besoin de remplacer Equals
(alors vous devrez remplacer en GetHashCode
conséquence).