Как 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");
}
}
}