2009-03-11 16 views
101

Wenn ich Objekte als Schlüssel für eine Dictionary verwenden möchte, welche Methoden muss ich außer Kraft setzen, um sie auf eine bestimmte Weise zu vergleichen?Verwenden eines Objekts als generischer Dictionary-Schlüssel

sagen, dass ich eine eine Klasse, die Eigenschaften hat:

class Foo { 
    public string Name { get; set; } 
    public int FooID { get; set; } 

    // elided 
} 

Und ich möchte ein erstellen:

Dictionary<Foo, List<Stuff>> 

Ich möchte Foo Objekte mit demselben FooID auf die gleiche Gruppe in Betracht gezogen werden. Welche Methoden muss ich in der Klasse Foo überschreiben?

Zusammengefasst: Ich möchte Stuff Objekte in Listen, gruppiert nach Foo Objekte. Stuff Objekte haben eine FooID, um sie mit ihrer Kategorie zu verknüpfen.

Antwort

128

Standardmäßig sind die beiden wichtigsten Methoden sind GetHashCode() und Equals(). Es ist wichtig, dass, wenn zwei Dinge gleich sind (Equals() gibt wahr zurück), sie denselben Hash-Code haben. Zum Beispiel könnten Sie "FooID zurückgeben". wie die GetHashCode() wenn Sie das als das Spiel wollen. Sie können auch IEquatable<Foo> implementieren, aber das ist optional:

class Foo : IEquatable<Foo> { 
    public string Name { get; set;} 
    public int FooID {get; set;} 

    public override int GetHashCode() { 
     return FooID; 
    } 
    public override bool Equals(object obj) { 
     return Equals(obj as Foo); 
    } 
    public bool Equals(Foo obj) { 
     return obj != null && obj.FooID == this.FooID; 
    } 
} 

schließlich eine weitere Alternative ein IEqualityComparer<T> zu schaffen, ist das gleiche zu tun.

+4

+1 und ich habe nicht die Absicht, diesen Thread zu entführen, aber ich hatte den Eindruck, dass GetHashCode() FooId.GetHashCode() zurückgeben sollte. Ist das nicht das richtige Muster? –

+7

@Ken - Nun, es muss nur ein int zurückgegeben werden, das die notwendigen Funktionen bietet. Welche FooID wird genauso gut funktionieren wie FooID.GetHashCode(). Als Implementierungsdetail ist Int32.GetHashCode() "gebe das zurück;". Für andere Typen (String etc), dann ja: .GetHashCode() wäre sehr nützlich. –

+2

Danke! Ich ging mit dem IEqualityComparer wie es nur für den Dicarionary war, dass ich die überschriebenen Methoden brauchte. – Dana

8

für Foo müssen Sie Object.GetHashCode() und object.Equals()

Das Wörterbuch wird rufen GetHashCode() außer Kraft zu setzen einen Hash-Behälter für jeden Wert zu berechnen und Equals zu vergleichen, ob identisch sind zwei Foo .

Stellen Sie sicher, gute Hash-Codes zu berechnen (vermeiden Sie viele gleiche Foo-Objekte mit dem gleichen Hashcode), aber stellen Sie sicher, dass zwei gleich Foos den gleichen Hash-Code haben. Vielleicht möchten Sie mit der Equals-Methode und dann (in GetHashCode()) mit dem Hash-Code jedes Elements beginnen, das Sie in Equals vergleichen.

public class Foo { 
    public string A; 
    public string B; 

    override bool Equals(object other) { 
      var otherFoo = other as Foo; 
      if (otherFoo == null) 
      return false; 
      return A==otherFoo.A && B ==otherFoo.B; 
    } 

    override int GetHashCode() { 
      return 17 * A.GetHashCode() + B.GetHashCode(); 
    } 
} 
+2

Abgesehen - aber xor (^) macht eine schlechte combinator für Hash-Codes, wie es oft zu vielen diagonalen Kollisionen führt (dh {"foo", "bar"} vs {"bar", "foo"}. Eine bessere Wahl ist es, jeden Ausdruck zu multiplizieren und hinzuzufügen - zB 17 * a.GetHashCode() + B.GetHashCode(); –

+2

Marc, I Sehen Sie, was Sie meinen. Aber wie erhalten Sie bei der magischen Zahl 17? Ist es vorteilhaft, eine Primzahl als Multiplikator für die Kombination von Hashes zu verwenden? Wenn ja, warum? – froh42

+0

Kann ich Ihnen vorschlagen: (A + B) .GetHashCode() statt: 17 * A.GetHashCode() + B.GetHashCode() Dies wird: 1) Weniger wahrscheinlich eine Kollision haben und 2) sicherstellen, dass es keinen ganzzahligen Überlauf gibt. –

29

Wie Sie die FooID sein die Kennung für die Gruppe aufnehmen möchten, sollten Sie das als Schlüssel im Wörterbuch anstelle des Objekts Foo verwenden:

Dictionary<int, List<Stuff>> 

Wenn Sie das Foo Objekt als Schlüssel verwenden, würden Sie würde nur die GetHashCode und Equals Methode implementieren, nur die FooID Eigenschaft zu berücksichtigen. Die Name Eigenschaft wäre nur totes Gewicht, soweit die Dictionary betroffen war, so würden Sie einfach Foo als Wrapper für eine int verwenden.

Daher ist es besser, den Wert FooID direkt zu verwenden, und dann müssen Sie nichts implementieren, da die Dictionary bereits unterstützt int als Schlüssel verwenden.

Edit:
Wenn Sie die Foo Klasse als Schlüssel trotzdem verwenden möchten, die IEqualityComparer<Foo> einfach zu implementieren:

public class FooEqualityComparer : IEqualityComparer<Foo> { 
    public int GetHashCode(Foo foo) { return foo.FooID.GetHashCode(); } 
    public bool Equals(Foo foo1, Foo foo2) { return foo1.FooID == foo2.FooID; } 
} 

Verbrauch:

Dictionary<Foo, List<Stuff>> dict = new Dictionary<Foo, List<Stuff>>(new FooEqualityComparer()); 
+1

Korrekt unterstützt int bereits die Methoden/Schnittstellen, die für die Verwendung als Schlüssel erforderlich sind. Das Wörterbuch hat keine direkte Kenntnis von int oder irgendeinem anderen Typ. –

+0

Ich dachte darüber nach, aber aus einer Vielzahl von Gründen war es sauberer und bequemer, die Objekte als Wörterbuchschlüssel zu verwenden. – Dana

+1

Nun, es sieht nur so aus, als ob Sie das Objekt als Schlüssel verwenden, da Sie wirklich nur die ID als Schlüssel verwenden. – Guffa

1

Was Hashtable Klasse!

Hashtable oMyDic = new Hashtable(); 
Object oAnyKeyObject = null; 
Object oAnyValueObject = null; 
oMyDic.Add(oAnyKeyObject, oAnyValueObject); 
foreach (DictionaryEntry de in oMyDic) 
{ 
    // Do your job 
} 

In oben Weise können Sie ein beliebiges Objekt (Klasse-Objekt) als generischen Dictionary-Schlüssel verwenden :)

0

Ich hatte das gleiche Problem. Ich kann jetzt jedes Objekt, das ich versucht habe, als Schlüssel verwenden, um Equals und GetHashCode zu überschreiben.

Hier ist eine Klasse, die ich mit Methoden innerhalb der Überschreibungen von Equals (Object Obj) und GetHashCode() zu erstellen. Ich entschied mich, Generika und einen Hash-Algorithmus zu verwenden, der in der Lage sein sollte, die meisten Objekte abzudecken. Bitte lassen Sie mich wissen, wenn Sie hier etwas sehen, das für einige Arten von Objekten nicht funktioniert und Sie haben eine Möglichkeit, es zu verbessern.

public class Equality<T> 
{ 
    public int GetHashCode(T classInstance) 
    { 
     List<FieldInfo> fields = GetFields(); 

     unchecked 
     { 
      int hash = 17; 

      foreach (FieldInfo field in fields) 
      { 
       hash = hash * 397 + field.GetValue(classInstance).GetHashCode(); 
      } 
      return hash; 
     } 
    } 

    public bool Equals(T classInstance, object obj) 
    { 
     if (ReferenceEquals(null, obj)) 
     { 
      return false; 
     } 
     if (ReferenceEquals(this, obj)) 
     { 
      return true; 
     } 
     if (classInstance.GetType() != obj.GetType()) 
     { 
      return false; 
     } 

     return Equals(classInstance, (T)obj); 
    } 

    private bool Equals(T classInstance, T otherInstance) 
    { 
     List<FieldInfo> fields = GetFields(); 

     foreach (var field in fields) 
     { 
      if (!field.GetValue(classInstance).Equals(field.GetValue(otherInstance))) 
      { 
       return false; 
      } 
     } 

     return true; 
    } 

    private List<FieldInfo> GetFields() 
    { 
     Type myType = typeof(T); 

     List<FieldInfo> fields = myType.GetTypeInfo().DeclaredFields.ToList(); 
     return fields; 
    } 
} 

Hier ist, wie es in einer Klasse verwendet wird:

public override bool Equals(object obj) 
    { 
     return new Equality<ClassName>().Equals(this, obj); 
    } 

    public override int GetHashCode() 
    { 
     unchecked 
     { 
      return new Equality<ClassName>().GetHashCode(this); 
     } 
    } 
Verwandte Themen