¿Cómo compara HashSet elementos para la igualdad?

Tengo una clase que es 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); } } 

Cuando agrego una lista de objetos de esta clase a un conjunto de hash:

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

Todo está bien y ha.count es 2 , pero:

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

Ahora ha.count es 3 .

  1. ¿Por qué HashSet no respeta el método CompareTo ?
  2. ¿Es HashSet la mejor manera de tener una lista de objetos únicos?

Utiliza un IEqualityComparer ( EqualityComparer.Default menos que especifique uno diferente en la construcción).

Cuando agrega un elemento al conjunto, encontrará el código hash utilizando IEqualityComparer.GetHashCode , y almacenará tanto el código hash como el elemento (después de verificar si el elemento ya está en el conjunto, por supuesto).

Para buscar un elemento, primero usará el IEqualityComparer.GetHashCode para encontrar el código hash, luego para todos los elementos con el mismo código hash, usará IEqualityComparer.Equals para comparar para la igualdad real.

Eso significa que tienes dos opciones:

  • Pase un IEqualityComparer en el constructor. Esta es la mejor opción si no puede modificar la T sí o si desea una relación de igualdad no predeterminada (por ejemplo, “todos los usuarios con una ID de usuario negativa se consideran iguales”). Esto casi nunca se implementa en el tipo en sí (es decir, Foo no implementa IEqualityComparer ), sino en un tipo separado que solo se utiliza para las comparaciones.
  • Implementar la igualdad en el tipo en sí, anulando GetHashCode e Equals(object) . Idealmente, implemente IEquatable en el tipo, especialmente si se trata de un tipo de valor. Estos métodos serán llamados por el comparador de igualdad predeterminado.

Tenga en cuenta que nada de esto es en términos de una comparación ordenada , lo que tiene sentido, ya que ciertamente hay situaciones en las que puede especificar fácilmente la igualdad pero no un ordenamiento total. Esto es todo lo mismo que Dictionary , básicamente.

Si desea un conjunto que utilice ordenamiento en lugar de solo comparaciones de igualdad, debe usar SortedSet de .NET 4, que le permite especificar un IComparer lugar de un IEqualityComparer . Esto usará IComparer.Compare – que delegará en IComparable.CompareTo o IComparable.CompareTo si está usando Comparer.Default .

Aquí hay una aclaración sobre una parte de la respuesta que no se ha dicho: el tipo de objeto de su HashSet no tiene que implementar IEqualityComparer sino que debe reemplazar a Object.GetHashCode() y Object.Equals(Object obj)

En lugar de esto:

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

Tu hiciste esto:

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

Es sutil, pero esto me hizo tropezar durante la mayor parte del día intentando que HashSet funcionara de la manera prevista. Y como han dicho otros, HashSet terminará llamando a.GetHashCode() y a.Equals(obj) según sea necesario cuando se trabaja con el conjunto.

HashSet usa Equals y GetHashCode() .

CompareTo es para conjuntos ordenados.

Si desea objetos únicos, pero no le importa su orden de iteración, HashSet suele ser la mejor opción.

El constructor HashSet recibe el objeto que implementa IEqualityComparer para agregar un nuevo objeto. si usas el método de uso en HashSet, debes reemplazar Iguales, 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 { 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 hashSet = new HashSet(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 hashSetB = new HashSet(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(hashSetB).ToList(); Display(union); var inter = hashSet.Intersect(hashSetB).ToList(); Display(inter); var except = hashSet.Except(hashSetB).ToList(); Display(except); Console.ReadKey(); } static void Display(HashSet 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 list) { if (list.Count == 0) { Console.WriteLine("Collection is Empty"); return; } foreach (var item in list) { Console.Write("{0}, ", item); } Console.Write("\n"); } } }