Wechseln oder ein Wörterbuch beim Zuweisen zu einem neuen Objekt

12

In letzter Zeit habe ich es vorgezogen, 1-1-Beziehungen Dictionariesanstelle von SwitchAnweisungen abzubilden . Ich finde es etwas schneller zu schreiben und mental einfacher zu verarbeiten. Leider möchte ich beim Zuordnen zu einer neuen Instanz eines Objekts dieses nicht so definieren:

var fooDict = new Dictionary<int, IBigObject>()
{
    { 0, new Foo() }, // Creates an instance of Foo
    { 1, new Bar() }, // Creates an instance of Bar
    { 2, new Baz() }  // Creates an instance of Baz
}

var quux = fooDict[0]; // quux references Foo

Angesichts dieses Konstrukts habe ich CPU-Zyklen und Speicher verschwendet, indem ich 3 Objekte erstellt habe, wobei ich alles getan habe, was ihre Konstruktoren enthalten könnten, und nur eines davon verwendet habe. Ich glaube auch, dass das Zuordnen anderer Objekte fooDict[0]in diesem Fall dazu führt, dass sie auf dasselbe verweisen, anstatt eine neue Instanz von Foowie beabsichtigt zu erstellen . Eine Lösung wäre, stattdessen ein Lambda zu verwenden:

var fooDict = new Dictionary<int, Func<IBigObject>>()
{
    { 0, () => new Foo() }, // Returns a new instance of Foo when invoked
    { 1, () => new Bar() }, // Ditto Bar
    { 2, () => new Baz() }  // Ditto Baz
}

var quux = fooDict[0](); // equivalent to saying 'var quux = new Foo();'

Kommt das an einen Punkt, an dem es zu verwirrend ist? Es ist leicht, das ()am Ende zu übersehen . Oder ist das Abbilden auf eine Funktion / einen Ausdruck eine ziemlich übliche Praxis? Die Alternative wäre, einen Schalter zu verwenden:

IBigObject quux;
switch(someInt)
{
    case 0: quux = new Foo(); break;
    case 1: quux = new Bar(); break;
    case 2: quux = new Baz(); break;
}

Welcher Aufruf ist akzeptabler?

  • Wörterbuch für schnellere Suche und weniger Stichwörter (case and break)
  • Schalter: Wird häufiger im Code verwendet und erfordert nicht die Verwendung eines Func <> -Objekts zur Indirektion.
KChaloux
quelle
2
Ohne Lambda wird bei jeder Suche mit demselben Schlüssel (wie in fooDict[0] is fooDict[0]) dieselbe Instanz zurückgegeben . sowohl beim lambda als auch beim switch ist dies nicht der fall
ratschenfreak
@ratchetfreak Ja, ich habe das tatsächlich bemerkt, als ich das Beispiel abtippte. Ich glaube, ich habe es irgendwo notiert.
KChaloux
1
Die Tatsache, dass Sie es explizit in eine Konstante setzen, bedeutet, dass das erstellte Objekt veränderbar sein muss. Aber wenn Sie sie eines Tages unveränderlich machen können, ist es die beste Lösung, das Objekt direkt zurückzugeben. Sie können das Diktat in ein konstantes Feld einfügen und die Kosten für die Erstellung nur einmal in der gesamten Anwendung übernehmen.
Laurent Bourgault-Roy

Antworten:

7

Das ist eine interessante Ansicht des Fabrikmusters . Ich mag die Kombination aus Dictionary und Lambda-Ausdruck. es hat mich dazu gebracht, diesen Behälter auf eine neue Art und Weise zu betrachten.

Ich ignoriere die Bedenken in Ihrer Frage zu CPU-Zyklen, wie Sie in den Kommentaren erwähnt haben, dass der Nicht-Lambda-Ansatz nicht das bietet, was Sie brauchen.

Ich denke, jeder Ansatz (Switch vs. Dictionary + Lambda) wird in Ordnung sein. Die einzige Einschränkung besteht darin, dass Sie mit dem Dictionary die Eingabetypen einschränken, die Sie zum Generieren der zurückgegebenen Klasse erhalten können.

Die Verwendung einer switch-Anweisung bietet Ihnen mehr Flexibilität bei den Eingabeparametern. Wenn dies jedoch ein Problem sein sollte, können Sie das Dictionary in eine Methode einschließen und das gleiche Endergebnis erzielen.

Wenn es für Ihr Team neu ist, kommentieren Sie den Code und erklären Sie, was los ist. Fordern Sie eine Team-Code-Überprüfung an, führen Sie sie durch das, was getan wurde, und machen Sie sie darauf aufmerksam. Davon abgesehen sieht es gut aus.


quelle
Leider besteht mein Team seit ungefähr einem Monat nur noch aus mir (der Lead kündigt). Ich habe nicht über die Relevanz für das Fabrikmuster nachgedacht. Das ist eigentlich eine nette Beobachtung.
KChaloux
1
@KChaloux: Wenn Sie nur das Factory-Methodenmuster verwenden, case 0: quux = new Foo(); break;wird case 0: return new Foo();dies natürlich genauso einfach zu schreiben und viel einfacher zu lesen als{ 0, () => new Foo() }
pdr
@pdr Das ist schon ein paar Stellen im Code aufgetaucht. Es gibt wahrscheinlich einen guten Grund, eine Factory-Methode für das Objekt zu erstellen, die diese Frage inspiriert hat, aber ich fand, dass es interessant genug ist, um es alleine zu stellen.
KChaloux
1
@KChaloux: Ich gebe zu, ich bin nicht begeistert von der jüngsten Besessenheit, Schalter / Koffer durch ein Wörterbuch zu ersetzen. Ich habe noch keinen Fall gesehen, in dem das Vereinfachen und Isolieren des Schalters in einer eigenen Methode effektiver gewesen wäre.
pdr
@pdr Obsession ist hier ein starkes Wort. Dies ist eher eine Überlegung bei der Entscheidung, wie mit einmaligen Zuordnungen zwischen Werten umgegangen werden soll. Ich bin damit einverstanden, dass es in Fällen, in denen es wiederholt wird, am besten ist, eine Erstellungsmethode zu isolieren.
KChaloux
7

C # 4.0 gibt Ihnen die Lazy<T>Klasse, die Ihrer eigenen zweiten Lösung ähnelt, aber "Lazy initialization" expliziter ausruft.

var fooDict = new Dictionary<int, Lazy<IBigObject>>()
{
    { 0, new Lazy(() => new Foo()) }, // Returns a new instance of Foo when invoked
    { 1, new Lazy(() => new Bar()) }, // Ditto Bar
    { 2, new Lazy(() => new Baz()) }  // Ditto Baz
}
Avner Shahar-Kashtan
quelle
Oh, das wusste ich nicht.
KChaloux
Oh, das ist schön!
Laurent Bourgault-Roy
2
Sobald Lazy.Value aufgerufen wird, wird dieselbe Instanz jedoch für die gesamte Lebensdauer verwendet. Siehe Lazy Initialization
Dan Lyons
Natürlich, sonst wäre es keine träge Initialisierung, sondern nur eine Neuinitialisierung jedes Mal.
Avner Shahar-Kashtan
Das OP gibt an, dass es jedes Mal zum Erstellen neuer Instanzen benötigt wird. Die zweite Lösung mit Lambdas und die dritte Lösung mit einem Switch tun dies, während die erste Lösung und die Lazy <T> -Implementierung dies nicht tun.
Dan Lyons
2

Stilistisch denke ich, dass die Lesbarkeit zwischen ihnen gleich ist. Es ist einfacher, Abhängigkeitsinjektionen mit der zu erstellen Dictionary.

Vergessen Sie nicht, dass Sie überprüfen müssen, ob der Schlüssel vorhanden ist, wenn Sie den verwenden Dictionary, und einen Fallback bereitstellen müssen, wenn dies nicht der Fall ist.

Ich würde die switchAnweisung für statische Codepfade und Dictionaryfür dynamische Codepfade (wo Sie Einträge hinzufügen oder entfernen können) bevorzugen . Der Compiler ist möglicherweise in der Lage, einige statische Optimierungen mit dem auszuführen switch, die er mit dem nicht ausführen kann Dictionary.

Interessanterweise ist dieses DictionaryMuster das, was Leute manchmal in Python tun, weil Python die switchAnweisung fehlt . Ansonsten verwenden sie If-else-Ketten.

M. Dudley
quelle
1

Im Allgemeinen würde ich auch nicht bevorzugen.

Was auch immer dies verbraucht, sollte mit einem funktionieren Func<int, IBigObject>. Die Quelle Ihres Mappings kann dann ein Dictionary oder eine Methode sein, die eine switch-Anweisung oder einen Web-Service-Aufruf oder eine Dateisuche hat ... wie auch immer.

Was die Implementierung angeht, würde ich das Wörterbuch vorziehen, da dies einfacher überarbeitet werden kann: "Hardcode-Wörterbuch, Suchschlüssel, Rückgabeergebnis" bis "Wörterbuch aus Datei laden, Suchschlüssel, Rückgabeergebnis".

Telastyn
quelle