Wie vergleicht HashSet Elemente für Gleichheit?

Ich habe eine class, die 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); } } 

Wenn ich eine Liste von Objekten dieser class zu einem Hash-Satz hinzufüge:

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

Alles ist in Ordnung und ha.count ist 2 , aber:

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

Jetzt ist ha.count 3 .

  1. Warum respektiert HashSet die CompareTo Methode nicht HashSet
  2. Ist HashSet der beste Weg, um eine Liste von einzigartigen Objekten zu haben?

Es verwendet einen IEqualityComparer ( EqualityComparer.Default sei denn, Sie geben einen anderen für die Konstruktion an).

Wenn Sie dem Set ein Element hinzufügen, findet es den Hash-Code mit IEqualityComparer.GetHashCode und speichert sowohl den Hash-Code als auch das Element (nachdem überprüft wurde, ob das Element bereits in der Menge ist).

Um ein Element nach oben zu sehen, wird zuerst der IEqualityComparer.GetHashCode , um den Hash-Code zu finden, und dann wird für alle Elemente mit demselben Hash-Code IEqualityComparer.Equals , um die tatsächliche Gleichheit zu vergleichen.

Das heißt, Sie haben zwei Möglichkeiten:

  • IEqualityComparer einen benutzerdefinierten IEqualityComparer in den Konstruktor. Dies ist die beste Option, wenn Sie das T selbst nicht ändern können oder wenn Sie eine nicht standardmäßige Gleichheitsbeziehung wünschen (z. B. “alle Benutzer mit einer negativen Benutzer-ID werden als gleich angesehen”). Dies wird fast nie auf dem Typ selbst implementiert (dh Foo implementiert IEqualityComparer ), sondern in einem separaten Typ, der nur für Vergleiche verwendet wird.
  • Implementieren Sie die Gleichheit im Typ selbst, indem Sie GetHashCode und Equals(object) überschreiben. Im Idealfall implementieren Sie IEquatable im Typ, insbesondere wenn es sich um einen IEquatable . Diese Methoden werden vom standardmäßigen Gleichheitsvergleicher aufgerufen.

Beachten Sie, dass dies nicht im Sinne eines geordneten Vergleichs geschieht – was sinnvoll ist, da es sicherlich Situationen gibt, in denen Sie leicht Gleichheit, aber keine Gesamtordnung angeben können. Das ist das selbe wie Dictionary .

Wenn Sie eine Menge verwenden möchten, die die Sortierung statt nur die Gleichheitsvergleiche verwendet, sollten Sie SortedSet von .NET 4 verwenden, mit dem Sie einen IComparer anstelle eines IEqualityComparer angeben können. Dies verwendet IComparer.Compare – das an IComparable.CompareTo oder IComparable.CompareTo wenn Sie Comparer.Default .

Hier ist eine HashSet zu einem Teil der Antwort, die nicht angegeben wurde: Der Objekttyp Ihres HashSet muss HashSet nicht implementieren, sondern muss nur Object.GetHashCode() und Object.Equals(Object obj) .

An Stelle von:

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

Du machst das:

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

Es ist subtil, aber das hat mich fast den ganzen Tag lang gestolpert, um zu erreichen, dass HashSet so funktioniert, wie es beabsichtigt ist. Und wie andere schon gesagt haben, wird HashSet am Ende a.GetHashCode() und a.Equals(obj) , wenn es mit dem Set arbeitet.

HashSet verwendet Equals und GetHashCode() .

CompareTo ist für geordnete Sets.

Wenn Sie eindeutige Objekte wünschen, die Reihenfolge der Iterationen jedoch nicht HashSet ist, ist HashSet normalerweise die beste Wahl.

Konstruktor HashSet empfängt Objekt, das IEqualityComparer zum Hinzufügen eines neuen Objekts implementiert. Wenn Sie Methode in HashSet verwenden, überschreiben Sie 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 { 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"); } } }