Ich finde mich oft dabei, Fragen online nachzuschlagen, und viele Lösungen enthalten Wörterbücher. Wenn ich jedoch versuche, sie zu implementieren, steckt dieser schreckliche Gestank in meinem Code. Zum Beispiel jedes Mal, wenn ich einen Wert verwenden möchte:
int x;
if (dict.TryGetValue("key", out x)) {
DoSomethingWith(x);
}
Das sind 4 Zeilen Code, um im Wesentlichen Folgendes zu tun: DoSomethingWith(dict["key"])
Ich habe gehört, dass die Verwendung des Schlüsselworts out ein Anti-Pattern ist, da dadurch Funktionen ihre Parameter verändern.
Außerdem brauche ich oft ein "umgekehrtes" Wörterbuch, in dem ich die Schlüssel und Werte umblättere.
In ähnlicher Weise möchte ich oft die Elemente in einem Wörterbuch durchlaufen und dabei Schlüssel oder Werte in Listen usw. konvertieren, um dies besser zu tun.
Ich habe das Gefühl, dass es fast immer eine bessere und elegantere Art gibt, Wörterbücher zu benutzen. Aber ich bin ratlos.
System.Collections.Generic
Namespace sind nicht threadsicher .Dictionary
, war das, was er wirklich wollte, eine neue Klasse. Für diese 50% (nur) ist es ein Designgeruch.Antworten:
Wörterbücher (C # oder anders) sind einfach ein Container, in dem Sie einen Wert basierend auf einem Schlüssel nachschlagen. In vielen Sprachen wird es korrekter als Map identifiziert, wobei die häufigste Implementierung eine HashMap ist.
Das zu berücksichtigende Problem ist, was passiert, wenn ein Schlüssel nicht vorhanden ist. Einige Sprachen verhalten sich durch Rücksendung
null
odernil
oder einen anderen Gegenwert. Stille Vorgabe eines Werts, anstatt Sie darüber zu informieren, dass kein Wert vorhanden ist.Die C # -Bibliotheksdesigner hatten sich eine Redewendung ausgedacht, um mit dem Verhalten umzugehen. Sie argumentierten, dass das Standardverhalten beim Suchen eines nicht vorhandenen Werts darin besteht, eine Ausnahme auszulösen. Wenn Sie Ausnahmen vermeiden möchten, können Sie die
Try
Variante verwenden. Dies ist der gleiche Ansatz, den sie zum Parsen von Zeichenfolgen in Ganzzahlen oder Datums- / Uhrzeitobjekte verwenden. Im Wesentlichen ist die Auswirkung wie folgt:Und das übertrug sich auf das Wörterbuch, dessen Indexer delegiert an
Get(index)
:Dies ist einfach die Art und Weise, wie die Sprache gestaltet ist.
Sollte
out
Variablen entmutigt werden?C # ist nicht die erste Sprache, die sie hat, und sie haben ihren Zweck in bestimmten Situationen. Wenn Sie versuchen, ein System mit hoher Gleichzeitigkeit zu erstellen, können Sie es nicht verwenden
out
Variablen an den Parallelitätsgrenzen verwenden.In vielerlei Hinsicht versuche ich, diese Redewendungen in meine APIs aufzunehmen, wenn es eine Redewendung gibt, die von den Anbietern von Sprach- und Kernbibliotheken vertreten wird. Dadurch fühlt sich die API in dieser Sprache konsistenter und besser aufgehoben. Eine in Ruby geschriebene Methode sieht also nicht wie eine in C #, C oder Python geschriebene Methode aus. Sie verfügen jeweils über eine bevorzugte Methode zum Erstellen von Code. Wenn Sie damit arbeiten, können die Benutzer der API diese schneller erlernen.
Sind Maps im Allgemeinen ein Anti-Pattern?
Sie haben ihren Zweck, aber oft sind sie die falsche Lösung für den Zweck, den Sie haben. Besonders wenn Sie eine bidirektionale Abbildung haben, die Sie benötigen. Es gibt viele Container und Möglichkeiten, Daten zu organisieren. Es gibt viele Ansätze, die Sie verwenden können, und manchmal müssen Sie ein wenig nachdenken, bevor Sie diesen Container auswählen.
Wenn Sie eine sehr kurze Liste bidirektionaler Zuordnungswerte haben, benötigen Sie möglicherweise nur eine Liste von Tupeln. Oder eine Liste von Strukturen, in denen Sie die erste Übereinstimmung auf beiden Seiten des Mappings finden können.
Denken Sie an die Problemdomäne und wählen Sie das am besten geeignete Werkzeug für den Job aus. Wenn es keinen gibt, dann erstelle ihn.
quelle
Option<T>
, dann würde das meiner Meinung nach die Verwendung der Methode in C # 2.0 erheblich erschweren, da es keine Mustererkennung gab.Einige gute Antworten hier auf die allgemeinen Prinzipien von Hashtabellen / Wörterbüchern. Aber ich dachte, ich würde Ihr Codebeispiel berühren,
Ab C # 7 (was meiner Meinung nach ungefähr zwei Jahre alt ist) kann dies vereinfacht werden zu:
Und natürlich könnte es auf eine Zeile reduziert werden:
Wenn Sie einen Standardwert für den Fall haben, dass der Schlüssel nicht vorhanden ist, kann er folgendermaßen lauten:
So können Sie kompakte Formulare erstellen, indem Sie relativ neue Spracherweiterungen verwenden.
quelle
"key"
nicht vorhanden,x
aufdefault(TValue)
"key".DoSomethingWithThis()
getOrElse("key", defaultValue)
Null-Objekt immer noch mein Lieblingsmuster ist. Arbeiten Sie auf diese Weise, und es ist Ihnen egal, ob dasTryGetValue
Ergebnis wahr oder falsch ist.TryGetValue
ist es außerdem nicht atomar / thread-sicher, sodass Sie genauso einfach eine Überprüfung auf Existenz und eine andere durchführen können, um den WertDies ist weder ein Codegeruch noch ein Anti-Pattern, da die Verwendung von TryGet-Funktionen mit einem out-Parameter ein idiomatisches C # ist. In C # stehen jedoch drei Optionen zur Verfügung, um mit einem Wörterbuch zu arbeiten. Sollten Sie also sicher sein, dass Sie die richtige für Ihre Situation verwenden. Ich glaube, ich weiß, woher das Gerücht kommt, dass es ein Problem mit einem out-Parameter gibt, also werde ich das am Ende behandeln.
Welche Funktionen sind beim Arbeiten mit einem C # -Wörterbuch zu verwenden?
Um dies zu rechtfertigen, muss nur auf die Dokumentation für Dictionary TryGetValue unter "Bemerkungen" verwiesen werden :
Der ganze Grund, warum TryGetValue existiert, besteht darin, die Verwendung von ContainsKey und Item [TKey] zu vereinfachen und gleichzeitig zu vermeiden, dass das Wörterbuch zweimal durchsucht werden muss Wahl.
In der Praxis habe ich aufgrund dieser einfachen Maxime selten ein unformatiertes Wörterbuch verwendet: Wählen Sie die allgemeinste Klasse / den allgemeinsten Container aus , der Ihnen die Funktionen bietet, die Sie benötigen . Das Wörterbuch wurde nicht für die Suche nach Werten und nicht nach Schlüsseln entwickelt. Wenn Sie dies wünschen, ist es möglicherweise sinnvoller, eine alternative Struktur zu verwenden. Ich glaube, ich habe Dictionary im letzten Jahr meines Entwicklungsprojekts vielleicht einmal verwendet, einfach weil es selten das richtige Werkzeug für den Job war, den ich versuchte. Dictionary ist sicherlich nicht das Schweizer Taschenmesser der C # -Toolbox.
Was ist los mit unseren Parametern?
CA1021: Vermeiden Sie Parameter
Vermutlich haben Sie dort gehört, dass unsere Parameter so etwas wie ein Anti-Pattern sind. Wie bei allen Regeln sollten Sie näher lesen, um das Warum zu verstehen. In diesem Fall wird sogar explizit erwähnt, dass das Try-Muster nicht gegen die Regel verstößt :
quelle
In C # -Wörterbüchern fehlen mindestens zwei Methoden, die meiner Meinung nach in vielen Situationen in anderen Sprachen den Code erheblich bereinigen. Der erste Befehl gibt einen zurück
Option
, mit dem Sie in Scala Code wie den folgenden schreiben können:Die zweite Methode gibt einen benutzerdefinierten Standardwert zurück, wenn der Schlüssel nicht gefunden wird:
Es gibt etwas zu sagen, um die Redewendungen zu verwenden, die eine Sprache bietet, wenn dies angemessen ist, wie das
Try
Muster, aber das bedeutet nicht, dass Sie nur das verwenden müssen, was die Sprache bietet. Wir sind Programmierer. Es ist in Ordnung, neue Abstraktionen zu erstellen, um das Verständnis unserer spezifischen Situation zu erleichtern, insbesondere, wenn dadurch viele Wiederholungen vermieden werden. Wenn Sie häufig etwas benötigen, z. B. Reverse-Lookups oder das Durchlaufen von Werten, können Sie dies durchführen. Erstellen Sie die Schnittstelle, die Sie sich gewünscht haben.quelle
Ich stimme zu, dass dies unelegant ist. Ein Mechanismus, den ich in diesem Fall gerne verwende, wenn der Wert ein Strukturtyp ist, ist:
Jetzt haben wir eine neue Version ,
TryGetValue
dass die Renditenint?
. Wir können dann einen ähnlichen Trick ausführen, um Folgendes zu erweiternT?
:Und jetzt füge es zusammen:
und wir sind auf eine einzige, klare Aussage zurückzuführen.
Ich würde etwas weniger stark formuliert sein und sagen, dass es eine gute Idee ist, Mutationen zu vermeiden, wenn dies möglich ist.
Implementieren oder beziehen Sie dann ein bidirektionales Wörterbuch. Sie sind einfach zu schreiben, oder es gibt viele Implementierungen im Internet. Hier gibt es eine Reihe von Implementierungen, zum Beispiel:
/programming/268321/bidirectional-1-to-1-dictionary-in-c-sharp
Klar, das machen wir alle.
Fragen Sie sich: "Angenommen, ich hätte eine andere Klasse als
Dictionary
diese, die genau die Operation implementiert, die ich ausführen möchte. Wie würde diese Klasse aussehen?" Wenn Sie diese Frage beantwortet haben, implementieren Sie diese Klasse . Du bist ein Computerprogrammierer. Lösen Sie Ihre Probleme, indem Sie Computerprogramme schreiben!quelle
Action<T>
, sehr ähnlich zu seininterface IAction<T> { void Invoke(T t); }
, aber mit sehr nachgiebigen Regeln darüber, was diese Schnittstelle "implementiert" und wieInvoke
sie aufgerufen werden kann. Wenn Sie mehr wissen möchten, informieren Sie sich in C # über "Delegaten" und anschließend über Lambda-Ausdrücke.Das
TryGetValue()
Konstrukt ist nur erforderlich, wenn Sie nicht wissen, ob "key" als Schlüssel im Wörterbuch vorhanden ist oder nicht, andernfallsDoSomethingWith(dict["key"])
ist es vollkommen gültig.Ein "weniger schmutziger" Ansatz könnte sein, ihn
ContainsKey()
stattdessen als Kontrolle zu verwenden.quelle
TryGetValue
ist, so schwer fällt es einem doch, den leeren Koffer in den Griff zu bekommen. Idealerweise würde ich mir wünschen, dass dies nur ein Optional zurückgibt.ContainsKey
Ansatz für Multithread-Anwendungen, bei dem es sich um die Sicherheitsanfälligkeit TOCTOU (Time-to-Time-of-Use) handelt. Was ist, wenn ein anderer Thread den Schlüssel zwischen den Aufrufen vonContainsKey
und löschtGetValue
?Optional<Optional<T>>
TryGetValue
aufConcurrentDictionary
. Das reguläre Wörterbuch würde nicht synchronisiert werdenAndere Antworten enthalten großartige Punkte, deshalb werde ich sie hier nicht wiederholen, sondern mich auf diesen Teil konzentrieren, der bislang weitgehend ignoriert zu werden scheint:
Tatsächlich ist es ziemlich einfach, ein Wörterbuch zu durchlaufen, da es Folgendes implementiert
IEnumerable
:Wenn Sie Linq bevorzugen, funktioniert das auch ganz gut:
Alles in allem finde ich Wörterbücher nicht als Antimuster - sie sind nur ein spezifisches Werkzeug mit spezifischen Verwendungszwecken.
Für noch mehr Details, check out
SortedDictionary
(verwendet RB-Bäume für eine vorhersagbarere Leistung) undSortedList
(das ist auch ein verwirrend benanntes Wörterbuch, das die Einfügungsgeschwindigkeit für die Suchgeschwindigkeit opfert, aber leuchtet, wenn Sie mit einer festen, vorab festgelegten sortierter Satz). Ich hatte einen Fall, in dem das Ersetzen von aDictionary
durch aSortedDictionary
zu einer um eine Größenordnung schnelleren Ausführung führte (aber es könnte auch umgekehrt passieren).quelle
Wenn Sie das Gefühl haben, dass die Verwendung eines Wörterbuchs umständlich ist, ist dies möglicherweise nicht die richtige Wahl für Ihr Problem. Wörterbücher sind großartig, aber wie ein Kommentator bemerkt hat, werden sie oft als Abkürzung für etwas verwendet, das eine Klasse sein sollte. Oder das Wörterbuch selbst kann als Kernspeichermethode in Frage kommen, sollte jedoch von einer Wrapper-Klasse umgeben sein, um die gewünschten Dienstmethoden bereitzustellen.
Es wurde viel über Dict [key] im Vergleich zu TryGet gesagt. Was ich häufig benutze, ist das Durchlaufen des Wörterbuchs mit KeyValuePair. Anscheinend ist dies ein weniger bekanntes Konstrukt.
Der Hauptvorteil eines Wörterbuchs ist, dass es im Vergleich zu anderen Sammlungen sehr schnell ist, wenn die Anzahl der Elemente zunimmt. Wenn Sie prüfen müssen, ob ein Schlüssel häufig vorhanden ist, möchten Sie sich möglicherweise fragen, ob die Verwendung eines Wörterbuchs angemessen ist. Als Kunde sollten Sie in der Regel wissen, was Sie dort eingeben und was sicher abgefragt werden kann.
quelle