web-dev-qa-db-fra.com

HashSet permet l'insertion d'éléments en double - C #

Ce genre de semble une question noob, mais je n'ai pas pu trouver de réponse à cette question spécifiquement.

J'ai cette classe:

public class Quotes{ 
    public string symbol; 
    public string extension
}

Et j'utilise ceci:

HashSet<Quotes> values = new HashSet<Quotes>();

Cependant, je peux ajouter plusieurs fois le même objet Quotes. Par exemple, mon objet Quotes peut avoir un 'symbole' égal à 'A' et une 'extension' égale à '= n', et cet objet Quotes apparaît plusieurs fois dans le HashSet (affichage du Hashset via le mode débogage). J'avais pensé qu'en appelant

values.Add(new Quotes(symb, ext));

avec le même symb et ext, 'false' serait retourné et l'élément ne serait pas ajouté. J'ai le sentiment que cela a quelque chose à voir avec la comparaison des objets Quotes lorsque le HashSet ajoute un nouvel objet. Toute aide serait grandement appréciée!

38
jpints14

Je suppose que vous créez un nouveau Quotes avec les mêmes valeurs. Dans ce cas, ils ne sont pas égaux. S'ils doivent être considérés comme égaux, remplacez les méthodes Equals et GetHashCode.

public class Quotes{ 
    public string symbol; 
    public string extension

    public override bool Equals(object obj)
    {
        Quotes q = obj as Quotes;
        return q != null && q.symbol == this.symbol && q.extension == this.Extension;
    }

    public override int GetHashCode()
    {
        return this.symbol.GetHashCode() ^ this.extension.GetHashCode();
    }
}
50
Kendall Frey

J'avais pensé qu'en appelant values.Add(new Quotes(symb, ext)); avec le même symb et ext, 'false' serait retourné et l'élément ne serait pas ajouté.

Ce n'est pas le cas.

HashSet utilisera GetHashCode et Equals pour déterminer l'égalité de vos objets. Pour l'instant, puisque vous ne remplacez pas ces méthodes dans Quotes, l'égalité de référence par défaut de System.Object Sera utilisée. Chaque fois que vous ajoutez un nouveau devis, il s'agit d'une instance d'objet unique, donc le HashSet le voit comme un objet unique.

Si vous remplacez Object.Equals Et Object.GetHashCode, Cela fonctionnera comme prévu.

19
Reed Copsey

Les HashSets comparent d'abord les entrées en fonction de leur hachage qui est calculé par GetHashCode.
L'implémentation par défaut renvoie un code de hachage basé sur l'objet lui-même (diffère entre chaque instance).

Seulement si les hachages sont identiques (très improbable pour les hachages basés sur des instances), la méthode Equals est appelée et utilisée pour comparer définitivement deux objets.

Vous avez des options:

  • Changer les devis en une structure
  • Remplacer GetHashCode et Equals dans les devis

Exemple:

 public override int GetHashCode()
 {
    return (this.symbol == null ? 0 : this.symbol.GetHashCode())
       ^ (this.extension == null ? 0 : this.extension.GetHashCode());
 }
 public override bool Equals(object obj)
 {
    if (Object.ReferenceEquals(this, obj))
      return true;

    Quotes other = obj as Quotes;
    if (Object.ReferenceEquals(other, null))
      return false;

    return String.Equals(obj.symbol, this.symbol)
        && String.Equals(obj.extension, this.extension);
 }
6
Matthias

Je voulais juste corriger quelque chose dans la réponse de Kendall (je ne peux pas commenter pour une raison étrange).

return this.symbol.GetHashCode() ^ this.extension.GetHashCode();

Notez que la fonction xor est une façon exceptionnellement sujette aux collisions de combiner deux hachages, surtout quand ils sont tous les deux du même type (puisque chaque objet où le symbole == extension sera haché en 0). Même lorsqu'ils ne sont pas du même type ou sont peu susceptibles d'être égaux les uns aux autres, il s'agit d'une mauvaise pratique, et s'y habituer peut provoquer des problèmes dans différents appareils.

Au lieu de cela, multipliez un hachage par un petit nombre premier et ajoutez le second, par exemple:

return 3 * this.symbol.GetHashCode() + this.extension.GetHashCode();
4
leetrobot

Je sais que c'est un peu tard, mais j'ai rencontré le même problème et j'ai trouvé un impact sur les performances inacceptable lors de la mise en œuvre de la réponse sélectionnée, surtout lorsque vous avez beaucoup d'enregistrements.

Je l'ai trouvé beaucoup plus rapide pour transformer cela en un processus en deux étapes en utilisant Hashset et Tuple et enfin en transformant via un Select.

public class Quotes{ 
    public string symbol; 
    public string extension
}

var values = new HashSet<Tuple<string,string>>();

values.Add(new Tuple<string,string>("A","=n"));
values.Add(new Tuple<string,string>("A","=n"));

// values.Count() == 1

values.Select (v => new Quotes{ symbol = v.Item1, extension = v.Item2 });
2
user1265146
Quotes q = new Quotes() { symbol = "GE", extension = "GElec" };
values.Add(q);
values.Add(q);

.. ajoute deux fois la même instance et renverra false la deuxième fois.

values.Add(new Quotes() { symbol = "GE", extension = "GElec" });
values.Add(new Quotes() { symbol = "GE", extension = "GElec" });

.. ajoute deux instances différentes qui ont les mêmes valeurs pour les champs publics.

Comme indiqué ailleurs, remplacer Equals et GetHashCode corrigera ceci:

public class Quotes { 
    public string symbol; 
    public string extension;

    public override bool Equals(object obj) {
        if (!(obj is Quotes)) { return false; }
        return (this.symbol == ((Quotes)obj).symbol) && 
               (this.extension == ((Quotes)obj).extension);
    }

    public override int GetHashCode() {
        return (this.symbol.GetHashCode()) ^ (this.extension.GetHashCode());
    }
} 

Si vous déboguez votre code par étapes, vous trouverez ces valeurs. Add appelle à la fois Quotes.Equals et Quotes.GetHashCode.

2
Joshua Honig