web-dev-qa-db-fra.com

Comment HashSet compare-t-il les éléments pour l'égalité?

J'ai une classe qui est IComparable:

public class a : IComparable
{
    public int Id { get; set; }
    public string Name { get; set; }

    public a(int id)
    {
        this.Id = id;
    }

    public int CompareTo(object obj)
    {
        return this.Id.CompareTo(((a)obj).Id);
    }
}

Quand j'ajoute une liste d'objets de cette classe à un ensemble de hachage:

a a1 = new a(1);
a a2 = new a(2);
HashSet<a> ha = new HashSet<a>();
ha.add(a1);
ha.add(a2);
ha.add(a1);

Tout va bien et ha.count est 2, mais:

a a1 = new a(1);
a a2 = new a(2);
HashSet<a> ha = new HashSet<a>();
ha.add(a1);
ha.add(a2);
ha.add(new a(1));

À présent ha.count est 3.

  1. Pourquoi HashSet ne respecte-t-il pas la méthode a de CompareTo?.
  2. Est-ce que HashSet est le meilleur moyen d'avoir une liste d'objets uniques?
109
nima

Il utilise un IEqualityComparer<T> ( EqualityComparer<T>.Default sauf si vous en spécifiez un autre lors de la construction).

Lorsque vous ajoutez un élément à l'ensemble, il trouve le code de hachage à l'aide de IEqualityComparer<T>.GetHashCode Et stocke à la fois le code de hachage et l'élément (après avoir vérifié si l'élément est déjà dans l'ensemble, bien sûr).

Pour rechercher un élément, il utilisera d'abord le IEqualityComparer<T>.GetHashCode Pour trouver le code de hachage, puis, pour tous les éléments ayant le même code de hachage, il utilisera IEqualityComparer<T>.Equals Pour comparer l'égalité réelle.

Cela signifie que vous avez deux options:

  • Passez une coutume IEqualityComparer<T> Dans le constructeur. C’est la meilleure option si vous ne pouvez pas modifier le T lui-même, ou si vous souhaitez une relation d’égalité autre que celle par défaut (par exemple, "tous les utilisateurs avec un ID utilisateur négatif sont considérés comme égaux"). Ceci n’est presque jamais implémenté sur le type lui-même (c.-à-d. Foo n’implémente pas IEqualityComparer<Foo>) Mais dans un type séparé qui n’est utilisé que pour les comparaisons.
  • Implémentez l'égalité dans le type lui-même, en surchargeant GetHashCode et Equals(object). Idéalement, implémentez IEquatable<T> Dans le type également, en particulier s'il s'agit d'un type de valeur. Ces méthodes seront appelées par le comparateur d'égalité par défaut.

Notez que rien de tout cela n’est en termes de comparaison ordonnée - ce qui a du sens, car il existe certainement des situations où vous pouvez facilement spécifier une égalité mais pas un total. commande. Ceci est identique à Dictionary<TKey, TValue>, En gros.

Si vous voulez un ensemble qui utilise la commande au lieu de simples comparaisons d’égalité, vous devez utiliser SortedSet<T> de. NET 4 - qui vous permet de spécifier un IComparer<T> Au lieu de IEqualityComparer<T>. Ceci utilisera IComparer<T>.Compare - qui déléguera à IComparable<T>.CompareTo Ou IComparable.CompareTo Si vous utilisez Comparer<T>.Default.

120
Jon Skeet

Voici des éclaircissements sur une partie de la réponse qui n'a pas été dite: le type d'objet de votre HashSet<T> Ne doit pas nécessairement implémenter IEqualityComparer<T>, Mais simplement écraser Object.GetHashCode() et Object.Equals(Object obj).

Au lieu de cela:

public class a : IEqualityComparer<a>
{
  public int GetHashCode(a obj) { /* Implementation */ }
  public bool Equals(a obj1, a obj2) { /* Implementation */ }
}

Tu fais cela:

public class a
{
  public override int GetHashCode() { /* Implementation */ }
  public override bool Equals(object obj) { /* Implementation */ }
}

C'est subtil, mais cela m'a fait trébucher pendant la majeure partie de la journée en essayant de faire fonctionner HashSet comme prévu. Et comme d’autres l’ont dit, HashSet<a> Finira par appeler a.GetHashCode() et a.Equals(obj) selon les besoins lors de l’utilisation de l’ensemble.

70
tyriker

HashSet utilise Equals et GetHashCode().

CompareTo est pour les ensembles ordonnés.

Si vous voulez des objets uniques, mais vous ne vous souciez pas de leur ordre d'itération, HashSet<T> est généralement le meilleur choix.

9
CodesInChaos

le constructeur HashSet reçoit un objet qui implémente IEqualityComparer pour ajouter un nouvel objet. si vous voulez utiliser une méthode dans HashSet, vous ne devez pas remplacer Equals, GetHashCode

namespace HashSet
{
    public class Employe
    {
        public Employe() {
        }

        public string Name { get; set; }

        public override string ToString()  {
            return Name;
        }

        public override bool Equals(object obj) {
            return this.Name.Equals(((Employe)obj).Name);
        }

        public override int GetHashCode() {
            return this.Name.GetHashCode();
        }
    }

    class EmployeComparer : IEqualityComparer<Employe>
    {
        public bool Equals(Employe x, Employe y)
        {
            return x.Name.Trim().ToLower().Equals(y.Name.Trim().ToLower());
        }

        public int GetHashCode(Employe obj)
        {
            return obj.Name.GetHashCode();
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            HashSet<Employe> hashSet = new HashSet<Employe>(new EmployeComparer());
            hashSet.Add(new Employe() { Name = "Nik" });
            hashSet.Add(new Employe() { Name = "Rob" });
            hashSet.Add(new Employe() { Name = "Joe" });
            Display(hashSet);
            hashSet.Add(new Employe() { Name = "Rob" });
            Display(hashSet);

            HashSet<Employe> hashSetB = new HashSet<Employe>(new EmployeComparer());
            hashSetB.Add(new Employe() { Name = "Max" });
            hashSetB.Add(new Employe() { Name = "Solomon" });
            hashSetB.Add(new Employe() { Name = "Werter" });
            hashSetB.Add(new Employe() { Name = "Rob" });
            Display(hashSetB);

            var union = hashSet.Union<Employe>(hashSetB).ToList();
            Display(union);
            var inter = hashSet.Intersect<Employe>(hashSetB).ToList();
            Display(inter);
            var except = hashSet.Except<Employe>(hashSetB).ToList();
            Display(except);

            Console.ReadKey();
        }

        static void Display(HashSet<Employe> hashSet)
        {
            if (hashSet.Count == 0)
            {
                Console.Write("Collection is Empty");
                return;
            }
            foreach (var item in hashSet)
            {
                Console.Write("{0}, ", item);
            }
            Console.Write("\n");
        }

        static void Display(List<Employe> list)
        {
            if (list.Count == 0)
            {
                Console.WriteLine("Collection is Empty");
                return;
            }
            foreach (var item in list)
            {
                Console.Write("{0}, ", item);
            }
            Console.Write("\n");
        }
    }
}
4
Nikolai Nechai