Ich habe einige Objekte in List, sagen wir, List<MyClass>
und MyClass hat mehrere Eigenschaften. Ich möchte einen Index der Liste basierend auf 3 Eigenschaften von MyClass erstellen. In diesem Fall sind 2 der Eigenschaften int und eine Eigenschaft ist eine Datums- / Uhrzeitangabe.
Grundsätzlich möchte ich in der Lage sein, etwas zu tun wie:
Dictionary< CompositeKey , MyClass > MyClassListIndex = Dictionary< CompositeKey , MyClass >();
//Populate dictionary with items from the List<MyClass> MyClassList
MyClass aMyClass = Dicitonary[(keyTripletHere)];
Manchmal erstelle ich mehrere Wörterbücher in einer Liste, um verschiedene Eigenschaften der darin enthaltenen Klassen zu indizieren. Ich bin mir jedoch nicht sicher, wie ich mit zusammengesetzten Schlüsseln am besten umgehen soll. Ich habe überlegt, eine Prüfsumme der drei Werte zu erstellen, aber dies birgt das Risiko von Kollisionen.
c#
dictionary
AaronLS
quelle
quelle
Antworten:
Sie sollten Tupel verwenden. Sie entsprechen einer CompositeKey-Klasse, aber Equals () und GetHashCode () sind bereits für Sie implementiert.
Oder mit System.Linq
Sofern Sie die Berechnung des Hash nicht anpassen müssen, ist die Verwendung von Tupeln einfacher.
Wenn Sie viele Eigenschaften in den zusammengesetzten Schlüssel aufnehmen möchten, kann der Name des Tupeltyps ziemlich lang werden. Sie können den Namen jedoch kürzer machen, indem Sie eine eigene Klasse erstellen, die von Tupel <...> abgeleitet ist.
** bearbeitet im Jahr 2017 **
Es gibt eine neue Option, die mit C # 7 beginnt: die Wertetupel . Die Idee ist dieselbe, aber die Syntax ist anders, leichter:
Der Typ
Tuple<int, bool, string>
wird(int, bool, string)
und der WertTuple.Create(4, true, "t")
wird(4, true, "t")
.Mit Wertetupeln wird es auch möglich, die Elemente zu benennen. Beachten Sie, dass sich die Leistungen geringfügig unterscheiden. Sie können daher ein Benchmarking durchführen, wenn dies für Sie von Bedeutung ist.
quelle
KeyValuePair<K,V>
und andere Strukturen haben eine Standard-Hash-Funktion, von der bekannt ist, dass sie fehlerhaft ist ( weitere Informationen finden Sie unter stackoverflow.com/questions/3841602/… ).Tuple<>
ist jedoch kein ValueType und seine Standard-Hash-Funktion verwendet zumindest alle Felder. Wenn das Hauptproblem Ihres Codes jedoch Kollisionen sind, implementieren Sie eine OptimierungGetHashCode()
, die Ihren Daten entspricht.Der beste Weg, den ich mir vorstellen kann, besteht darin, eine CompositeKey-Struktur zu erstellen und die Methoden GetHashCode () und Equals () zu überschreiben, um Geschwindigkeit und Genauigkeit bei der Arbeit mit der Sammlung sicherzustellen:
Ein MSDN-Artikel zu GetHashCode ():
http://msdn.microsoft.com/en-us/library/system.object.gethashcode.aspx
quelle
Wie wäre es mit
Dictionary<int, Dictionary<int, Dictionary<DateTime, MyClass>>>
?Dies würde Ihnen Folgendes ermöglichen:
quelle
CompositeDictionary<TKey1, TKey2, TValue>
(etc), der einfach vonDictionary<TKey1, Dictionary<TKey2, TValue>>
(oder wie viele verschachtelte Wörterbücher auch immer erforderlich sind ) erbt . Ohne den gesamten Typ von Grund auf selbst zu implementieren (anstatt mit zu betrügen verschachtelte Wörterbücher oder Typen, die die Schlüssel enthalten) Dies ist das schnellste, das wir bekommen.Sie können sie in einer Struktur speichern und als Schlüssel verwenden:
Link zum Abrufen des Hash-Codes: http://msdn.microsoft.com/en-us/library/system.valuetype.gethashcode.aspx
quelle
Tuple
s. Dies ist also eine gute Lösung!Nachdem VS2017 / C # 7 veröffentlicht wurde, ist die beste Antwort die Verwendung von ValueTuple:
Ich habe das Wörterbuch mit einem anonymen ValueTuple deklariert
(string, string, int)
. Aber ich hätte ihnen Namen geben können(string name, string path, int id)
.Perfekt ist das neue ValueTuple schneller als Tuple,
GetHashCode
aber langsamerEquals
. Ich denke, Sie müssten vollständige End-to-End-Experimente durchführen, um herauszufinden, welches für Ihr Szenario wirklich am schnellsten ist. Aber die End-to-End-Freundlichkeit und Sprachsyntax für ValueTuple macht es zu einem Gewinn.quelle
Zwei Ansätze fallen mir sofort ein:
Tun Sie, was Kevin vorgeschlagen hat, und schreiben Sie eine Struktur, die als Schlüssel dient. Stellen Sie sicher, dass diese Struktur implementiert
IEquatable<TKey>
und ihreEquals
undGetHashCode
Methoden * überschrieben werden.Schreiben Sie eine Klasse, die intern verschachtelte Wörterbücher verwendet. Etwas wie:
TripleKeyDictionary<TKey1, TKey2, TKey3, TValue>
... diese Klasse würde intern hat ein Mitglied des TypsDictionary<TKey1, Dictionary<TKey2, Dictionary<TKey3, TValue>>>
, und würde Methoden aus , wie zBthis[TKey1 k1, TKey2 k2, TKey3 k3]
,ContainsKeys(TKey1 k1, TKey2 k2, TKey3 k3)
usw.* Ein Wort dazu, ob das Überschreiben der
Equals
Methode erforderlich ist: ZwarEquals
vergleicht die Methode für eine Struktur standardmäßig den Wert jedes Mitglieds, verwendet jedoch die Reflexion, die von Natur aus Leistungskosten verursacht, und ist daher nicht sehr wichtig angemessene Implementierung für etwas, das als Schlüssel in einem Wörterbuch verwendet werden soll (meiner Meinung nach jedenfalls). Gemäß der MSDN-Dokumentation zuValueType.Equals
:quelle
Wenn der Schlüssel Teil der Klasse ist, verwenden Sie
KeyedCollection
.Hier wird
Dictionary
der Schlüssel vom Objekt abgeleitet.Unter der Decke ist es Wörterbuch.
Sie müssen den Schlüssel im
Key
und nicht wiederholenValue
.Warum ein Risiko eingehen, ist der Schlüssel nicht der gleiche
Key
wie derValue
.Sie müssen nicht dieselben Informationen im Speicher duplizieren.
KeyedCollection-Klasse
Indexer, um den zusammengesetzten Schlüssel verfügbar zu machen
Für die Verwendung des Werttyps fpr empfiehlt der Schlüssel, den Microsoft ausdrücklich empfiehlt.
ValueType.GetHashCode
Tuple
ist technisch gesehen kein Werttyp, leidet jedoch unter demselben Symptom (Hash-Kollisionen) und ist kein guter Kandidat für einen Schlüssel.quelle
HashSet<T>
eine entsprechendeIEqualityComparer<T>
Option ebenfalls möglich. Übrigens, ich denke, Ihre Antwort wird Stimmen anziehen, wenn Sie Ihre Klassennamen und andere Mitgliedsnamen ändern können :)Darf ich eine Alternative vorschlagen - ein anonymes Objekt. Es ist dasselbe, das wir in der GroupBy LINQ-Methode mit mehreren Schlüsseln verwenden.
Es mag seltsam aussehen, aber ich habe Tuple.GetHashCode und neue {a = 1, b = 2} .GetHashCode-Methoden verglichen und die anonymen Objekte gewinnen auf meinem Computer unter .NET 4.5.1:
Objekt - 89.1732 ms für 10000 Anrufe in 1000 Zyklen
Tupel - 738.4475 ms für 10000 Anrufe in 1000 Zyklen
quelle
dictionary[new { a = my_obj, b = 2 }]
folgt verwenden, ist der resultierende Hash-Code eine Kombination aus my_obj.GetHashCode und ((Int32) 2) .GetHashCode.Eine andere Lösung für die bereits erwähnten wäre, eine Art Liste aller bisher generierten Schlüssel zu speichern. Wenn ein neues Objekt generiert wird, generieren Sie dessen Hashcode (nur als Ausgangspunkt). Überprüfen Sie, ob es bereits in der Liste enthalten ist Fügen Sie dann einen zufälligen Wert usw. hinzu, bis Sie einen eindeutigen Schlüssel haben. Speichern Sie diesen Schlüssel dann im Objekt selbst und in der Liste und geben Sie ihn jederzeit als Schlüssel zurück.
quelle