Как HashSet сравнивает элементы для равенства?

У меня есть класс 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);
    }
}

Когда я добавляю список объектов этого класса в хэш-набор:

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

Все нормально, а ha.count - 2, но:

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

Теперь ha.count есть 3.

  • Почему метод HashSet не соответствует a CompareTo.
  • Есть ли HashSet лучший способ иметь список уникальных объектов?

Ответ 1

Он использует IEqualityComparer<T> (EqualityComparer<T>.Default если вы не указали другую конструкцию).

Когда вы добавляете элемент в набор, он найдет хэш-код с помощью IEqualityComparer<T>.GetHashCode и сохранит как хэш-код, так и элемент (после проверки, действительно ли элемент уже установлен в комплекте).

Чтобы посмотреть элемент вверх, он сначала будет использовать IEqualityComparer<T>.GetHashCode для поиска хеш-кода, затем для всех элементов с одинаковым хеш-кодом он будет использовать IEqualityComparer<T>.Equals для сравнения для фактического равенства.

Обратите внимание на то, что это не так, как с помощью упорядоченного сравнения, что имеет смысл, поскольку есть определенные ситуации, когда вы можете легко определить равенство, но не полный порядок. Это все равно как Dictionary<TKey, TValue>, в основном.

Если вам нужен набор, который использует упорядочение, а не просто сравнения сравнений, вы должны использовать SortedSet<T> из .NET 4, что позволяет вам для указания IComparer<T> вместо IEqualityComparer<T>. Это будет использовать IComparer<T>.Compare - который будет делегировать IComparable<T>.CompareTo или IComparable.CompareTo, если вы используете Comparer<T>.Default.

Ответ 2

Здесь пояснение части ответа, которое не было учтено: Тип объекта вашего HashSet<T> не должен реализовывать IEqualityComparer<T>, а вместо этого просто должен переопределить Object.GetHashCode() и Object.Equals(Object obj).

Вместо этого:

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

Вы делаете это:

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

Это тонко, но это помогло мне в течение большей части дня, пытаясь заставить HashSet функционировать так, как он предназначен. И, как говорили другие, HashSet<a> в конечном итоге вызовет a.GetHashCode() и a.Equals(obj) по мере необходимости при работе с набором.

Ответ 3

HashSet использует Equals и GetHashCode().

CompareTo для упорядоченных множеств.

Если вам нужны уникальные объекты, но вам не нужен их порядок итерации, HashSet<T>, как правило, лучший выбор.

Ответ 4

конструктор HashSet получает объект, который реализует IEqualityComparer для добавления нового объекта. если вы хотите использовать метод использования в HashSet, вы не переопределяете 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");
        }
    }
}