J'ai une structure en C #:
public struct UserInfo
{
public string str1
{
get;
set;
}
public string str2
{
get;
set;
}
}
La seule règle est que UserInfo(str1="AA", str2="BB").Equals(UserInfo(str1="BB", str2="AA"))
Comment remplacer la fonction GetHashCode pour cette structure?
MSDN :
Une fonction de hachage doit avoir les propriétés suivantes:
- Si deux objets se comparent comme égaux, la méthode
GetHashCode
pour chaque objet doit renvoyer la même valeur. Cependant, si deux objets ne se comparent pas de manière égale, les méthodesGetHashCode
pour les deux objets ne doivent pas renvoyer de valeurs différentes.- La méthode
GetHashCode
pour un objet doit toujours renvoyer le même code de hachage tant qu'il n'y a pas de modification de l'état de l'objet qui détermine la valeur de retour de la méthodeEquals
de l'objet. Notez que cela n'est vrai que pour l'exécution en cours d'une application et qu'un code de hachage différent peut être renvoyé si l'application est réexécutée.- Pour de meilleures performances, une fonction de hachage doit générer une distribution aléatoire pour toutes les entrées.
La prendre en compte de manière correcte c'est:
return str1.GetHashCode() ^ str2.GetHashCode()
^
peut être remplacé par une autre opération commutative
Voir réponse de Jon Skeet - opérations binaires comme ^
ne sont pas bons, ils génèrent souvent du hash en collision!
public override int GetHashCode()
{
unchecked
{
return (str1 ?? String.Empty).GetHashCode() +
(str2 ?? String.Empty).GetHashCode();
}
}
L'utilisation de l'opérateur '+' peut être meilleure que l'utilisation de '^', car bien que vous souhaitiez explicitement que ('AA', 'BB') et ('BB', 'AA') soient explicitement les mêmes, vous pouvez ne pas vouloir ( 'AA', 'AA') et ('BB', 'BB') doivent être les mêmes (ou toutes les paires égales d'ailleurs).
La règle "aussi vite que possible" n'est pas entièrement respectée dans cette solution car dans le cas des valeurs nulles, cela effectue un "GetHashCode ()" sur la chaîne vide plutôt que de renvoyer immédiatement une constante connue, mais même sans mesurer explicitement, je suis prêt pour risquer de deviner que la différence ne serait pas assez importante pour vous inquiéter, sauf si vous vous attendez à beaucoup de nullités.
En règle générale, un moyen simple de générer un code de hachage pour une classe est de XOR tous les champs de données qui peuvent participer à la génération du code de hachage (en prenant soin de vérifier null comme indiqué par Cela répond également à l'exigence (artificielle?) que les codes de hachage pour UserInfo ("AA", "BB") et UserInfo ("BB", "AA") sont les mêmes.
Si vous pouvez faire des hypothèses sur l'utilisation de votre classe, vous pouvez peut-être améliorer votre fonction de hachage. Par exemple, s'il est courant que str1 et str2 soient identiques, XOR peut ne pas être un bon choix. Mais si str1 et str2 représentent, disons, prénom et nom, XOR est probablement un bon choix.
Bien que ce ne soit clairement pas censé être un exemple du monde réel, il peut être utile de souligner que: - Ceci est probablement un mauvais exemple d'utilisation d'une structure: Une structure devrait normalement avoir une sémantique de valeur, qui ne semble pas être le cas ici. - L'utilisation de propriétés avec des setters pour générer un code de hachage pose également problème.
Une manière simple générale est de faire ceci:
return string.Format("{0}/{1}", str1, str2).GetHashCode();
Sauf si vous avez des exigences de performances strictes, c'est la plus simple à laquelle je peux penser et j'utilise fréquemment cette méthode lorsque j'ai besoin d'une clé composite. Il gère très bien les cas null
et ne provoquera (m) aucune collision de hachage (en général). Si vous attendez "/" dans vos chaînes, choisissez simplement un autre séparateur auquel vous ne vous attendez pas.
Dans le même esprit, ReSharper suggère:
public int GetHashCode()
{
unchecked
{
int hashCode;
// String properties
hashCode = (hashCode * 397) ^ (str1!= null ? str1.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (str2!= null ? str1.GetHashCode() : 0);
// int properties
hashCode = (hashCode * 397) ^ intProperty;
return hashCode;
}
}
397 est un nombre premier de taille suffisante pour provoquer un débordement de la variable de résultat et mélanger quelque peu les bits du hachage, offrant une meilleure distribution des codes de hachage. Sinon, il n'y a rien de spécial en 397 qui le distingue des autres nombres premiers de même ampleur.
public override int GetHashCode()
{
unchecked
{
return(str1 != null ? str1.GetHashCode() : 0) ^ (str2 != null ? str2.GetHashCode() : 0);
}
}
Ah oui, comme l'a souligné Gary Shutler:
return str1.GetHashCode() + str2.GetHashCode();
Peut déborder. Vous pouvez essayer de diffuser aussi longtemps qu'Artem l'a suggéré, ou vous pouvez entourer l'instruction dans le mot clé non contrôlé:
return unchecked(str1.GetHashCode() + str2.GetHashCode());
Essayez celui-ci:
(((long)str1.GetHashCode()) + ((long)str2.GetHashCode())).GetHashCode()
De nombreuses possibilités. Par exemple.
return str1.GetHashCode() ^ str1.GetHashCode()
Peut-être quelque chose comme str1.GetHashCode () + str2.GetHashCode ()? ou (str1.GetHashCode () + str2.GetHashCode ())/2? De cette façon, ce serait la même chose que str1 et str2 soient échangés ....
Triez-les, puis concaténez-les:
return ((str1.CompareTo (str2) <1)? str1 + str2: str2 + str1) .GetHashCode ();
Le résultat de GetHashCode est censé être:
En gardant cela à l'esprit, j'irais avec quelque chose comme ceci:
if (str1 == null)
if (str2 == null)
return 0;
else
return str2.GetHashCode();
else
if (str2 == null)
return str1.GetHashCode();
else
return ((ulong)str1.GetHashCode() | ((ulong)str2.GetHashCode() << 32)).GetHashCode();
Edit: Oublié les nulls. Code fixe.