Étant donné la classe suivante
public class Foo
{
public int FooId { get; set; }
public string FooName { get; set; }
public override bool Equals(object obj)
{
Foo fooItem = obj as Foo;
if (fooItem == null)
{
return false;
}
return fooItem.FooId == this.FooId;
}
public override int GetHashCode()
{
// Which is preferred?
return base.GetHashCode();
//return this.FooId.GetHashCode();
}
}
J'ai remplacé la Equals
méthode car Foo
représente une ligne pour la Foo
table s. Quelle est la méthode préférée pour remplacer le GetHashCode
?
Pourquoi est-il important de passer outre GetHashCode
?
c#
overriding
hashcode
David Basarab
la source
la source
Réponses:
Oui, il est important que votre élément soit utilisé comme clé dans un dictionnaire, ou
HashSet<T>
, etc. - car il est utilisé (en l'absence de coutumeIEqualityComparer<T>
) pour regrouper les éléments dans des compartiments. Si le code de hachage pour deux éléments ne correspond pas, ils peuvent ne jamais être considérés comme égaux ( Equals ne sera simplement jamais appelé).La méthode GetHashCode () doit refléter la
Equals
logique; les règles sont les suivantes:Equals(...) == true
), elles doivent renvoyer la même valeur pourGetHashCode()
GetHashCode()
est égal, il n'est pas nécessaire qu'ils soient identiques; c'est une collision, etEquals
sera appelé pour voir s'il s'agit d'une réelle égalité ou non.Dans ce cas, il semble que "
return FooId;
" soit uneGetHashCode()
implémentation appropriée . Si vous testez plusieurs propriétés, il est courant de les combiner en utilisant du code comme ci-dessous, pour réduire les collisions diagonales (c'est-à-dire de sorte qu'ellesnew Foo(3,5)
aient un code de hachage différentnew Foo(5,3)
):Oh - pour plus de commodité, vous pouvez également envisager de fournir
==
et des!=
opérateurs lors de la substitution deEquals
etGetHashCode
.Une démonstration de ce qui se passe lorsque vous vous trompez est ici .
la source
Il est en fait très difficile à implémenter
GetHashCode()
correctement car, en plus des règles que Marc a déjà mentionnées, le code de hachage ne devrait pas changer pendant la durée de vie d'un objet. Par conséquent, les champs utilisés pour calculer le code de hachage doivent être immuables.J'ai finalement trouvé une solution à ce problème lorsque je travaillais avec NHibernate. Mon approche consiste à calculer le code de hachage à partir de l'ID de l'objet. L'ID ne peut être défini que par le constructeur, donc si vous voulez changer l'ID, ce qui est très peu probable, vous devez créer un nouvel objet qui a un nouvel ID et donc un nouveau code de hachage. Cette approche fonctionne mieux avec les GUID car vous pouvez fournir un constructeur sans paramètre qui génère aléatoirement un ID.
la source
En remplaçant Equals, vous déclarez essentiellement que vous êtes celui qui sait mieux comparer deux instances d'un type donné, vous êtes donc probablement le meilleur candidat pour fournir le meilleur code de hachage.
Voici un exemple de la façon dont ReSharper écrit une fonction GetHashCode () pour vous:
Comme vous pouvez le voir, il essaie simplement de deviner un bon code de hachage basé sur tous les champs de la classe, mais puisque vous connaissez le domaine ou les plages de valeurs de votre objet, vous pouvez toujours en fournir un meilleur.
la source
0 ^ a = a
donc0 ^ m_someVar1 = m_someVar1
. Il pourrait tout aussi bien définir la valeur initiale deresult
àm_someVar1
.N'oubliez pas de vérifier le paramètre obj
null
lors de la substitutionEquals()
. Et comparez également le type.La raison en est:
Equals
doit renvoyer false lors de la comparaison avecnull
. Voir également http://msdn.microsoft.com/en-us/library/bsc2ak47.aspxla source
obj
est en effet égalthis
quelle que soit la façon dont Equals () de la classe de base a été appelée.fooItem
vers le haut puis la vérification de la valeur Null fonctionnera mieux dans le cas de NULL ou d'un type incorrect.obj as Foo
serait invalide.Que diriez-vous:
la source
string.Format
. Un autre geek que j'ai vu estnew { prop1, prop2, prop3 }.GetHashCode()
. Je ne peux pas dire si l'on serait plus lent entre ces deux. N'abusez pas des outils.{ prop1="_X", prop2="Y", prop3="Z" }
et{ prop1="", prop2="X_Y", prop3="Z_" }
. Vous ne voulez probablement pas ça.Nous avons deux problèmes à résoudre.
Vous ne pouvez pas indiquer
GetHashCode()
si un champ de l'objet peut être modifié. Souvent, un objet ne sera JAMAIS utilisé dans une collection qui en dépendGetHashCode()
. Ainsi, le coût de mise en œuvreGetHashCode()
n'en vaut souvent pas la peine, ou ce n'est pas possible.Si quelqu'un place votre objet dans une collection qui appelle
GetHashCode()
et que vous avez remplacéEquals()
sans également vousGetHashCode()
comporter correctement, cette personne peut passer des jours à dépister le problème.Par conséquent, je le fais par défaut.
la source
GetHashCode
fonction telle que deux objets égaux renvoient le même code de hachage;return 24601;
etreturn 8675309;
seraient tous deux des implémentations valides deGetHashCode
. Les performances deDictionary
ne seront décentes que lorsque le nombre d'éléments est petit et deviendront très mauvaises si le nombre d'éléments devient important, mais cela fonctionnera correctement dans tous les cas.C'est parce que le framework requiert que deux objets identiques doivent avoir le même hashcode. Si vous remplacez la méthode equals pour effectuer une comparaison spéciale de deux objets et que les deux objets sont considérés comme identiques par la méthode, le code de hachage des deux objets doit également être le même. (Les dictionnaires et les tables de hachage reposent sur ce principe).
la source
Juste pour ajouter les réponses ci-dessus:
Si vous ne remplacez pas Equals, le comportement par défaut est que les références des objets sont comparées. La même chose s'applique au hashcode - l'implémentation par défaut est généralement basée sur une adresse mémoire de la référence. Étant donné que vous avez remplacé Equals, cela signifie que le comportement correct consiste à comparer tout ce que vous avez implémenté sur Equals et non les références, vous devez donc faire de même pour le code de hachage.
Les clients de votre classe s'attendront à ce que le hashcode ait une logique similaire à la méthode equals, par exemple les méthodes linq qui utilisent un IEqualityComparer comparent d'abord les hashcodes et seulement s'ils sont égaux ils compareront la méthode Equals () qui pourrait être plus chère pour s'exécuter, si nous n'avons pas implémenté le code de hachage, un objet égal aura probablement des codes de hachage différents (car ils ont une adresse mémoire différente) et sera déterminé à tort comme non égal (Equals () ne sera même pas atteint).
En outre, à l'exception du problème selon lequel vous ne pourrez peut-être pas trouver votre objet si vous l'utilisez dans un dictionnaire (car il a été inséré par un code de hachage et lorsque vous le recherchez, le code de hachage par défaut sera probablement différent et à nouveau égal à () ne sera même pas appelé, comme l'explique Marc Gravell dans sa réponse, vous introduisez également une violation du dictionnaire ou du concept de hashset qui ne devrait pas autoriser des clés identiques - vous avez déjà déclaré que ces objets sont essentiellement les mêmes lorsque vous écrasez Equals donc vous ne Je ne veux pas les deux comme des clés différentes sur une structure de données qui supposent avoir une clé unique. Mais parce qu'ils ont un code de hachage différent, la "même" clé sera insérée comme différente.
la source
Le code de hachage est utilisé pour les collections basées sur le hachage comme Dictionary, Hashtable, HashSet etc. Le but de ce code est de pré-trier très rapidement un objet spécifique en le plaçant dans un groupe spécifique (bucket). Ce pré-tri aide énormément à trouver cet objet lorsque vous devez le récupérer à partir de la collection de hachage car le code doit rechercher votre objet dans un seul compartiment au lieu de tous les objets qu'il contient. La meilleure distribution des codes de hachage (meilleure unicité) la récupération plus rapide. Dans une situation idéale où chaque objet a un code de hachage unique, le trouver est une opération O (1). Dans la plupart des cas, il s'approche de O (1).
la source
Ce n'est pas nécessairement important; cela dépend de la taille de vos collections et de vos exigences de performances et si votre classe sera utilisée dans une bibliothèque où vous ne connaissez peut-être pas les exigences de performances. Je sais souvent que mes tailles de collection ne sont pas très grandes et mon temps est plus précieux que quelques microsecondes de performances gagnées en créant un code de hachage parfait; donc (pour se débarrasser de l'avertissement ennuyeux du compilateur) j'utilise simplement:
(Bien sûr, je pourrais également utiliser un #pragma pour désactiver l'avertissement, mais je préfère cette méthode.)
Lorsque vous êtes dans la position que vous avez besoin de la performance que tous les problèmes mentionnés par d' autres appliquent ici, bien sûr. Plus important - sinon, vous obtiendrez des résultats erronés lors de la récupération d'éléments à partir d'un ensemble de hachage ou d'un dictionnaire: le code de hachage ne doit pas varier avec la durée de vie d'un objet (plus précisément, à chaque fois que le code de hachage est nécessaire, par exemple en étant une clé dans un dictionnaire): par exemple, ce qui suit est faux car Value est public et peut donc être modifié en externe pour la classe pendant la durée de vie de l'instance, vous ne devez donc pas l'utiliser comme base pour le code de hachage:
D'un autre côté, si la valeur ne peut pas être modifiée, il est correct d'utiliser:
la source
Vous devez toujours garantir que si deux objets sont égaux, tels que définis par Equals (), ils doivent renvoyer le même code de hachage. Comme certains autres commentaires le disent, en théorie, cela n'est pas obligatoire si l'objet ne sera jamais utilisé dans un conteneur basé sur le hachage comme HashSet ou Dictionary. Je vous conseillerais de toujours suivre cette règle. La raison en est tout simplement parce qu'il est beaucoup trop facile pour quelqu'un de changer une collection d'un type à un autre avec la bonne intention d'améliorer réellement les performances ou simplement de transmettre la sémantique du code d'une meilleure manière.
Par exemple, supposons que nous conservions certains objets dans une liste. Quelque temps plus tard, quelqu'un se rend réellement compte qu'un HashSet est une bien meilleure alternative en raison des meilleures caractéristiques de recherche par exemple. C'est à ce moment que nous pouvons avoir des ennuis. La liste utiliserait en interne le comparateur d'égalité par défaut pour le type qui signifie égal dans votre cas tandis que HashSet utilise GetHashCode (). Si les deux se comportent différemment, votre programme aussi. Et gardez à l'esprit que ces problèmes ne sont pas les plus faciles à résoudre.
J'ai résumé ce comportement avec d'autres pièges GetHashCode () dans un article de blog où vous pouvez trouver d'autres exemples et explications.
la source
La
.NET 4.7
méthode préférée de remplacementGetHashCode()
est indiquée ci-dessous. Si vous ciblez des versions plus anciennes de .NET, incluez le package de nuget System.ValueTuple .En termes de performances, cette méthode surclassera la plupart des implémentations de code de hachage composite . Le ValueTuple est
struct
donc il n'y aura pas de déchets, et l'algorithme sous-jacent est aussi rapide que possible.la source
Je crois comprendre que le GetHashCode () d'origine renvoie l'adresse mémoire de l'objet, il est donc essentiel de la remplacer si vous souhaitez comparer deux objets différents.
EDITED: C'était incorrect, la méthode GetHashCode () d'origine ne peut pas garantir l'égalité de 2 valeurs. Bien que les objets égaux renvoient le même code de hachage.
la source
Ci-dessous, l'utilisation de la réflexion me semble une meilleure option compte tenu des propriétés publiques, car avec cela, vous n'avez pas à vous soucier de l'ajout / de la suppression de propriétés (bien que ce ne soit pas un scénario si courant). J'ai également constaté que cela fonctionnait mieux (temps comparé avec le chronomètre Diagonistics).
la source