web-dev-qa-db-fra.com

Comment implémentez-vous GetHashCode pour la structure avec deux chaînes, lorsque les deux chaînes sont interchangeables

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?

66
Graviton

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éthodes GetHashCode 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éthode Equals 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

66
aku

Voir réponse de Jon Skeet - opérations binaires comme ^ ne sont pas bons, ils génèrent souvent du hash en collision!

26
Tomáš Kafka
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.

15
Jerry
  1. 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.

  2. 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.

5
Joe

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.

4

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.

3
Jani Hyytiäinen
public override int GetHashCode()   
{       
    unchecked      
    {           
        return(str1 != null ? str1.GetHashCode() : 0) ^ (str2 != null ? str2.GetHashCode() : 0);       
    }   
}
3
user11556

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());
2
Grokys

Essayez celui-ci:

(((long)str1.GetHashCode()) + ((long)str2.GetHashCode())).GetHashCode()
1
Artem Tikhomirov

De nombreuses possibilités. Par exemple.

return str1.GetHashCode() ^ str1.GetHashCode()

0
VolkerK

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 ....

0
Mike Stone

Triez-les, puis concaténez-les:

 return ((str1.CompareTo (str2) <1)? str1 + str2: str2 + str1) 
 .GetHashCode (); 
0
Steve Morgan

Le résultat de GetHashCode est censé être:

  1. Aussi vite que possible.
  2. Aussi unique que possible.

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.

0
Omer van Kloeten