Jak HashSet porównuje elementy dla równości?

Mam klasę 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);
    }
}

Kiedy dodam listę obiektów tej klasy do zbioru hashów:

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);

Wszystko jest w porządku i ha.count jest 2, ale:

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));

Teraz ha.count jest 3.

  1. dlaczego HashSet nie respektuje a metody CompareTo.
  2. Czy HashSet jest najlepszym sposobem na posiadanie listy unikalnych obiektów?
 90
Author: nima, 2012-01-21

4 answers

Używa IEqualityComparer<T> (EqualityComparer<T>.Default chyba, że podasz inny na budowie).

Gdy dodasz element do zestawu, znajdzie on kod skrótu za pomocą IEqualityComparer<T>.GetHashCode i zapisze zarówno kod skrótu, jak i element (oczywiście po sprawdzeniu, czy element jest już w zestawie).

Aby wyszukać element, najpierw użyje IEqualityComparer<T>.GetHashCode, aby znaleźć kod skrótu, a następnie dla wszystkich elementów z tym samym kodem skrótu użyje IEqualityComparer<T>.Equals, aby porównać rzeczywiste równość.

Oznacza to, że masz dwie opcje:

  • przekazuje własne IEqualityComparer<T> do konstruktora. Jest to najlepsza opcja, jeśli nie możesz zmodyfikować samej T lub jeśli chcesz mieć domyślną relację równości (np. "wszyscy użytkownicy z ujemnym ID użytkownika są traktowani jako równi"). To prawie nigdy nie jest zaimplementowane na samym typie (tzn. Foo nie implementuje IEqualityComparer<Foo>), ale w oddzielnym typie, który jest używany tylko do porównań.
  • zaimplementuj równość w samym typie, przez nadrzędne GetHashCode i Equals(object). Najlepiej zaimplementować IEquatable<T> również w typie, szczególnie jeśli jest to typ wartości. Metody te będą wywoływane przez domyślny porównywacz równości.

Zauważ, że nic z tego nie jest w kategoriach uporządkowanego porównania - co ma sens, ponieważ są sytuacje, w których można łatwo określić równość, ale nie całkowite uporządkowanie. To wszystko jest takie samo jak Dictionary<TKey, TValue>, W zasadzie.

Jeśli chcesz zamiast tego zestaw, który używa ordering tylko porównań równości, należy użyć SortedSet<T> Z. NET 4-który pozwala na podanie IComparer<T> zamiast IEqualityComparer<T>. To użyje IComparer<T>.Compare - które deleguje do IComparable<T>.CompareTo lub IComparable.CompareTo, Jeśli używasz Comparer<T>.Default.

 97
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-01 07:10:10

Oto Wyjaśnienie Części odpowiedzi, która została niewypowiedziana: typ obiektu twojego HashSet<T> nie musi implementować IEqualityComparer<T>, ale zamiast tego musi zastąpić Object.GetHashCode() i Object.Equals(Object obj).

Zamiast tego:

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

Robisz to:

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

To jest subtelne, ale to poruszyło mnie przez większą część dnia próbując sprawić, by HashSet działał tak, jak jest zamierzony. I tak jak inni mówili, HashSet<a> skończy się na wywołaniu a.GetHashCode() i a.Equals(obj) w razie potrzeby podczas pracy z gotowi.

 64
Author: tyriker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-12 08:54:56

HashSet używa Equals i GetHashCode().

CompareTo jest dla zamówionych zestawów.

Jeśli chcesz unikalnych obiektów, ale nie dbasz o ich kolejność iteracji, HashSet<T> jest zazwyczaj najlepszym wyborem.

 9
Author: CodesInChaos,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-21 09:40:50

Konstruktor HashSet odbiera obiekt, który implementuje Ieequalitycomparer do dodania nowego obiektu. jeśli chcesz użyć metody w HashSet, możesz użyć overrride 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");
        }
    }
}
 3
Author: Nikolai Nechai,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-25 12:44:14