136 votes

Directives de GetHashCode en c#

J’ai lu dans l’essentiel de c# 3.0 et .NET 3.5 livre qui :

Retours de GetHashCode () au cours de la vie d’un objet particulier doivent être constante (la même valeur), même si les données de l’objet changent. Dans de nombreux cas, vous devez mettre en cache le retour de la méthode pour appliquer ceci.

N’est-ce une ordonnance valide ?

J’ai essayé quelques types intégrés dans .NET et ils ne se comportent comme ça.

120voto

Alex Points 1107

Il a été un long temps, mais néanmoins, je pense qu'il est encore nécessaire de donner une réponse correcte à cette question, y compris des explications sur le pourquoi et le comment. La meilleure réponse à ce jour est celui citant le MSDN exhaustivly - n'essayez pas de faire vos propres règles, le MME gars savaient ce qu'ils faisaient.

Mais tout d'abord: La Directive citée dans la question est mal.

Maintenant, le pourquoi - il y en a deux

D'abord pourquoi: Si le hashcode est calculée de façon à ce qu'il ne change pas pendant la durée de vie d'un objet, même si l'objet lui-même change, qu'elle allait se briser l'égalité contrat.

Souvenez-vous: "Si deux objets sont considérées comme égales, la méthode GetHashCode pour chaque objet doit retourner la même valeur. Toutefois, si deux objets ne sont pas considérées comme égales, la GetHashCode méthodes pour les deux objets n'ont pas à renvoyer des valeurs différentes."

La deuxième phrase est souvent interprété à tort comme "La seule règle est, qu'à l'objet de la création, le hashcode de l'égalité des objets doit être égale". Ne sais pas vraiment pourquoi, mais c'est l'essence même de la plupart des réponses ici.

Pensez à deux objets contenant un nom, où le nom est utilisé dans la méthode equals: Même nom -> même chose. Créer Une Instance: Nom = Joe Créer Une Instance De B: Nom = Pierre

Hashcode Un et Hashcode B seront plus susceptibles de ne pas être la même. Ce qui allait arriver maintenant, quand le Nom de l'instance de B est modifié à Joe?

Selon la directive de la question, le hashcode de B ne changerait pas. Le résultat de ce serait: A. Equals(B) ==> vrai Mais dans le même temps: A. GetHashCode() == B. GetHashCode() ==> false.

Mais, justement, ce comportement est interdit explicitement par the equals et hashcode-contrat.

Deuxième pourquoi: Alors qu'il est - bien sûr - c'est vrai, que les changements dans le hashcode pourrait casser haché listes et d'autres objets à l'aide de la hashcode, l'inverse est vrai aussi. Ne pas changer le hashcode sera dans le pire des cas obtenir haché listes, où tous de tout un tas de différents objets ont le même hashcode et donc être dans le même hachage bin - se produit lorsque les objets sont initialisés avec une valeur standard, par exemple.


Arrive maintenant à le faire Eh bien, à première vue, il semble y avoir une contradiction - de toute façon, le code de casser. Mais ni le problème vient de changé ou inchangé hashcode.

La source des problèmes est bien décrite dans la MSDN:

À partir de MSDN de la table de hachage entrée:

Les objets de clé doivent être immuables tant comme ils sont utilisés comme clés dans le Table de hachage.

Cela signifie:

Tout objet qui crée un hashvalue devrait changer la hashvalue, lorsque l'objet de modifications, mais il ne doit pas - ne doit absolument pas - de permettre des modifications à lui-même, lorsqu'il est utilisé à l'intérieur d'une table de hachage (ou tout autre de Hachage de l'objet, bien sûr).

D'abord comment Façon la plus simple serait bien sûr de la conception des objets immuables uniquement pour l'utilisation dans des tables de hachage, qui sera créé en tant que copys de la normale, les objets mutables en cas de besoin. À l'intérieur de la des objets immuables, c'est obviusly ok pour mettre en cache le hashcode, puisqu'il est immuable.

Deuxième comment Ou donner l'objet d'un "vous êtes haché maintenant"-drapeau, assurez-vous que toutes les données de l'objet est privé, vérifiez l'indicateur dans toutes les fonctions qui peuvent modifier les objets de données et de lever une exception de données si le changement n'est pas autorisé (c'est à dire le drapeau est réglé). Maintenant, quand vous mettez l'objet dans toute haché zone, assurez-vous de mettre le drapeau, et - ainsi - unset le drapeau, quand il n'est plus nécessaire. Pour la facilité d'utilisation, je vous conseille de fixer le drapeau automatiquement à l'intérieur de la "GetHashCode" méthode - de cette façon, il ne peut pas être oublié. Et l'appel explicite d'un "ResetHashFlag" méthode fera en sorte que le programmeur devra penser, si elle est ou n'est pas autorisé à modifier les objets de données.

Ok, ce qui devrait être dit ainsi: Il y a des cas, où il est possible d'avoir des objets avec des données mutable, où le hashcode est néanmoins inchangée, lorsque les objets de données est modifié, sans violer la equals et hashcode-contrat.

Cela nécessite, cependant, que l'égalité méthode n'est pas basée sur les données mutable. Donc, si j'écris un objet, et de créer une méthode GetHashCode qui ne calculer qu'une fois une valeur et les stocke à l'intérieur de l'objet pour revenir plus tard appels, puis je dois, encore une fois: absolument, créer une méthode Equals, qui va utiliser les valeurs enregistrées pour la comparaison, de sorte que A. Equals(B) ne changera jamais de faux à vrai. Autrement, le contrat serait rompu. Le résultat de ce sera généralement que la méthode Equals n'a aucun sens, c'est pas la référence d'origine est égal, mais ce n'est ni une valeur égale. Parfois, cela peut être l'intention de comportement (c'est à dire des dossiers clients), mais généralement, il ne l'est pas.

Donc, il suffit de faire GetHashCode résultat du changement, les données de l'objet de modifications, et si l'utilisation de l'objet à l'intérieur de hachage à l'aide des listes ou des objets est prévu (ou juste possible), puis faire l'objet soit immuable, soit créer un readonly indicateur à utiliser pour la durée de vie d'un hachage de la liste contenant l'objet.

(En passant: Tout cela n'est pas C# oder .NET spécifiques - c'est dans la nature de tous les hashtable implémentations, ou plus généralement de toute liste indexée, que les données d'identification des objets ne doit jamais changer, tandis que l'objet est dans la liste. Inattendue et imprévisible d'un comportement, si cette règle est enfreinte. Quelque part, il peut y avoir la liste des mises en œuvre, que faire surveiller tous les éléments à l'intérieur de la liste et n'automatique réindexation de la liste - mais la performance de ceux qui vont sûrement être horrible au mieux.)

93voto

Jeff Yates Points 36725

La réponse est la plupart du temps, c'est une bonne ligne directrice, mais peut-être pas une règle valide. Il également ne pas raconter toute l'histoire.

Le point étant faite que pour mutable types, vous ne pouvez pas baser le code de hachage sur les données mutable parce que deux objets égaux doivent retourner le même code de hachage et le code de hachage doit être valide pour la durée de vie de l'objet. Si le code de hachage changements, vous vous retrouvez avec un objet qui se perd dans un hachage de la collection parce qu'il ne vit plus dans le bon hash bin.

Par exemple, l'objet de retours de hachage de 1. Donc, il va dans la corbeille de 1 de la table de hachage. Ensuite, vous modifiez l'objet d'Une telle qu'elle retourne une valeur de hachage de 2. Lorsqu'une table de hachage va à la recherche pour elle, il se regarde dans la corbeille de 2 et ne peut pas trouver l'objet est orphelin dans le bac 1. C'est pourquoi le code de hachage ne doit pas changer pendant la durée de vie de l'objet, et une des raisons pourquoi l'écriture GetHashCode implémentations est une douleur dans le cul.

Mise à jour
Eric Lippert a posté un blog qui donne d'excellentes informations sur GetHashCode.

Mise À Jour Supplémentaire
J'ai fait quelques modifications ci-dessus:

  1. J'ai fait une distinction entre la directive et de la règle.
  2. J'ai frappé à travers "pour la durée de vie de l'objet".

Une ligne directrice est juste un guide, pas une règle. En réalité, GetHashCode n'a qu'à suivre ces lignes directrices lorsque les choses s'attendre à l'objet à suivre les lignes directrices, comme lorsqu'elle est stockée dans une table de hachage. Si vous n'avez jamais l'intention d'utiliser vos objets dans les tables de hachage (ou n'importe quoi d'autre qui s'appuie sur les règles d' GetHashCode), votre application n'a pas besoin de suivre les lignes directrices.

Quand vous voyez "pour la durée de vie de l'objet", vous devriez lire "pour le moment, l'objet doit coopérer avec les tables de hachage" ou similaire. Comme la plupart des choses, GetHashCode est de savoir quand de briser les règles.

9voto

Jon B Points 26872

À partir de MSDN

Si deux objets sont considérées comme égales, les Méthode GetHashCode pour chaque objet doit retourner la même valeur. Cependant, si deux objets ne comparez pas comme l'égalité, la GetHashCode méthodes pour l' deux objets n'ont pas de retour des valeurs différentes.

La méthode GetHashCode pour un objet doit constamment revenir par le même hash code tant qu'il n'y est pas modification de l'état de l'objet que détermine la valeur de retour de la objet de la méthode Equals. Notez que cette est vrai que pour l'exécution en cours d'une demande, et qu'un les différents code de hachage peut être retourné si l'application est exécutée à nouveau.

Pour la meilleure performance, une table de hachage fonction de génération aléatoire d' de distribution pour toutes les entrées.

Cela signifie que si la valeur(s) de la modification de l'objet, le code de hachage doit changer. Par exemple, une "Personne" de la classe avec la propriété "Name" réglé sur "Tom" doit avoir un code de hachage, et un autre code si vous modifiez le nom de "Jerry". Sinon, Tom == Jerry, qui n'est probablement pas ce que vous avez prévu.


Edit:

Aussi à partir de MSDN:

Les classes dérivées qui remplacent GetHashCode doit également remplacer Équivaut à garantir que les deux objets considérés comme égaux ont le même code de hachage; sinon, la table de hachage de type peut ne pas fonctionner correctement.

À partir de MSDN de la table de hachage entrée:

Les objets de clé doivent être immuables tant qu'ils sont utilisés comme clés dans la table de hachage.

La façon dont j'ai lu c'est que mutable objets doit renvoyer différentes hashcodes que leurs valeurs changent, sauf s' ils sont conçus pour une utilisation dans une table de hachage.

Dans l'exemple du Système.De dessin.Là, l'objet est mutable, et de ne retourner un autre hashcode lorsque la valeur X ou Y change. Ce serait faire un mauvais candidat pour être utilisé comme tel dans une table de hachage.

9voto

Frederik Gheysels Points 36354

Je pense que la documentation sur les GetHashcode est un peu déroutant.

D'une part, de MSDN indique que le hashcode d'un objet ne change jamais , et être constant D'autre part, MSDN indique également que la valeur de retour de GetHashcode doit être le même pour les 2 objets, si ces 2 objets sont considérés comme étant égaux.

MSDN:

Une fonction de hachage doit avoir les propriétés suivantes:

  • Si deux objets sont considérées comme égales, la méthode GetHashCode pour chaque objet doit retourner la même valeur. Cependant, si deux objets ne comparez pas comme l'égalité, la GetHashCode méthodes pour l' deux objets n'ont pas de retour des valeurs différentes.
  • La méthode GetHashCode pour un objet doit constamment revenir l' même code de hachage tant qu'il n'y est pas modification de l'état de l'objet que détermine la valeur de retour de la objet de la méthode Equals. Notez que cette est vrai que pour l'exécution en cours d'une demande, et qu'un les différents code de hachage peut être retourné si l'application est exécutée à nouveau.
  • Pour de meilleures performances, une fonction de hachage doit générer un hasard de distribution pour toutes les entrées.

Ensuite, cela signifie que tous les objets doivent être immuable, ou la méthode GetHashcode devrait être basée sur les propriétés de votre objet qui sont immuables. Supposons par exemple que vous avez cette classe (implémentation naïve):

public class SomeThing
{
      public string Name {get; set;}

      public override GetHashCode()
      {
          return Name.GetHashcode();
      }

      public override Equals(object other)
      {
           SomeThing = other as Something;
           if( other == null ) return false;
           return this.Name == other.Name;
      }
}

Cette mise en œuvre déjà enfreint les règles qui peuvent être trouvés dans MSDN. Supposons que vous disposez de 2 instances de cette classe; le Nom de la propriété de instance1 est réglé sur "Pol", et le Nom de la propriété de instance2 est "Piet'. Les deux instances de retourner un autre hashcode, et ils ne sont également pas d'égal. Maintenant, supposons que je change le Nom de instance2 à "Pol", alors, selon ma méthode Equals, les deux instances doivent être égaux, et selon une des règles de MSDN, ils doivent retourner le même hashcode.
Cependant, cela ne peut pas être fait, car le hashcode de instance2 va changer, et MSDN indique que ce n'est pas autorisé.

Alors, si vous avez une entité, vous pourriez peut-être mettre en œuvre le hashcode de sorte qu'il utilise la "identifiant primaire" de cette entité, qui est peut-être, idéalement, une clé de substitution, ou une propriété immuable. Si vous avez un objet de valeur, vous pouvez mettre en œuvre le Hashcode de sorte qu'il utilise les propriétés de l'objet de valeur. Ces propriétés font de la "définition" de l'objet de valeur. C'est bien sûr la nature d'un objet de valeur; vous n'êtes pas intéressé dans son identité, mais plutôt dans la valeur.
Et, par conséquent, des objets de valeur doivent être immuable. (Juste comme ils sont dans le .NET framework, string, Date, etc... sont tous des objets immuables).

Une autre chose qui vient à l'esprit:
Au cours de laquelle 'session' (je ne sais pas vraiment comment je dois appeler ce) devrait "GetHashCode' retourner une valeur constante. Supposons que vous ouvrez votre application, charger une instance d'un objet en dehors de la bd (une entité), et d'obtenir son hashcode. Il sera de retour un certain nombre. Fermez l'application, et de charger la même entité. Est-il nécessaire que le hashcode de cette période, a la même valeur que lors du chargement de l'entité pour la première fois ? À mon humble avis, non.

8voto

Justin R. Points 10122

Il est de bon conseil. Voici ce que Brian Pepin a à dire sur le sujet:

Cela a déclenché m'en hausse de plus de une fois: assurez-vous toujours GetHashCode renvoie la même valeur à travers le la durée de vie d'une instance. Rappelez-vous que des codes de hachage sont utilisées pour identifier les "seaux" dans la plupart des hashtable les implémentations. Si un objet "seau" change, une table de hachage peut pas être en mesure de trouver votre objet. Ceux-ci peuvent être très dur de bugs à trouver, donc l'obtenir dès la première fois.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X