Switch o un dizionario durante l'assegnazione a un nuovo oggetto


12

Di recente, sono venuto a preferire la mappatura delle relazioni 1-1 utilizzando Dictionariesanziché le Switchistruzioni. Trovo che sia un po 'più veloce da scrivere e più facile da elaborare mentalmente. Sfortunatamente, quando si mappa su una nuova istanza di un oggetto, non voglio definirlo in questo modo:

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

Dato quel costrutto, ho sprecato i cicli della CPU e la memoria creando 3 oggetti, facendo tutto ciò che i loro costruttori potrebbero contenere e alla fine ne ho usato solo uno. Credo anche che la mappatura di altri oggetti fooDict[0]in questo caso li indurrà a fare riferimento alla stessa cosa, piuttosto che creare una nuova istanza Foocome previsto. Una soluzione sarebbe invece usare un lambda:

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

Sta arrivando a un punto in cui è troppo confuso? È facile perderlo ()alla fine. O la mappatura su una funzione / espressione è una pratica abbastanza comune? L'alternativa sarebbe usare un interruttore:

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

Quale invocazione è più accettabile?

  • Dizionario, per ricerche più rapide e meno parole chiave (maiuscole e minuscole)
  • Switch: più comunemente presente nel codice, non richiede l'uso di un oggetto Func <> per l'indirizzamento indiretto.

2
senza lambda avrai la stessa istanza restituita ogni volta che esegui la ricerca con la stessa chiave (come in fooDict[0] is fooDict[0]). sia con la lambda che con l'interruttore questo non è il caso
maniaco del cricchetto

@ratchetfreak Sì, in realtà me ne sono reso conto mentre stavo scrivendo l'esempio. Penso di averne preso nota da qualche parte.
KChaloux,

1
Immagino che il fatto che tu l'abbia esplicitamente indicato in una costante significhi che l'oggetto creato deve essere mutabile. Ma se un giorno puoi renderli immutabili, restituire direttamente l'oggetto sarà la soluzione migliore. È possibile inserire il dict in un campo const e sostenere il costo della creazione una sola volta nell'intera applicazione.
Laurent Bourgault-Roy,

Risposte:


7

È un'interpretazione interessante del modello di fabbrica . Mi piace la combinazione del Dizionario e dell'espressione lambda; mi ha fatto guardare quel contenitore in un modo nuovo.

Sto ignorando la preoccupazione nella tua domanda sui cicli della CPU come hai menzionato nei commenti che l'approccio non lambda non fornisce ciò di cui hai bisogno.

Penso che entrambi gli approcci (switch vs. Dictionary + lambda) andranno bene. L'unica limitazione è che usando il Dizionario, stai limitando i tipi di input che potresti ricevere per generare la classe restituita.

L'uso di un'istruzione switch fornirebbe maggiore flessibilità sui parametri di input. Tuttavia, se questo dovesse essere un problema, potresti racchiudere il Dizionario all'interno di un metodo e avere lo stesso risultato finale.

Se è nuovo per il tuo team, commenta il codice e spiega cosa sta succedendo. Chiama per una revisione del codice del team, guidali attraverso ciò che è stato fatto e rendili consapevoli. A parte questo, sembra a posto.


Sfortunatamente, a partire da circa un mese fa, la mia squadra è composta esclusivamente da me (il lead smesso). Non pensavo alla sua rilevanza per il modello di fabbrica. Questa è un'osservazione accurata, in realtà.
KChaloux,

1
@KChaloux: Certo, se stavi usando solo il modello Metodo di fabbrica, il tuo case 0: quux = new Foo(); break;diventa case 0: return new Foo();che è francamente facile da scrivere e molto più facile da leggere rispetto a{ 0, () => new Foo() }
pdr

@pdr Questo è già mostrato in alcuni punti del codice. Probabilmente c'è una buona ragione per creare un metodo di fabbrica sull'oggetto che ha ispirato questa domanda, ma ho pensato che fosse abbastanza interessante da fare da solo.
KChaloux,

1
@KChaloux: confesso che non sono appassionato della recente ossessione per la sostituzione di switch / case con un dizionario. Non ho ancora visto un caso in cui semplificare e isolare l'interruttore nel suo metodo non sarebbe stato più efficace.
pdr,

@pdr Obsession è una parola forte da usare qui. Più di una considerazione quando si decide come gestire i mapping una tantum tra i valori. Sono d'accordo che nei casi in cui è ripetuto, è meglio isolare un metodo di creazione.
KChaloux,

7

C # 4.0 ti dà la Lazy<T>classe, che è simile alla tua seconda soluzione, ma grida più esplicitamente "Inizializzazione pigra".

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
}

Ooh, non lo sapevo.
KChaloux,

Oh, questo è carino!
Laurent Bourgault-Roy,

2
Tuttavia, una volta richiamato Lazy.Value, utilizza la stessa istanza per tutta la sua durata. Vedi Lazy Initialization
Dan Lyons,

Naturalmente, altrimenti non sarebbe un'inizializzazione pigra, reinizializzazione ogni volta.
Avner Shahar-Kashtan,

L'OP afferma che ne ha bisogno per creare nuove istanze ogni volta. La seconda soluzione con lambda e la terza soluzione con uno switch lo fanno entrambe, mentre la prima soluzione e l'implementazione Lazy <T> no.
Dan Lyons,

2

Stilisticamente penso che la leggibilità sia uguale tra loro. È più facile fare l'iniezione di dipendenza con Dictionary.

Non dimenticare che è necessario verificare se la chiave esiste quando si utilizza Dictionarye, in caso contrario, fornire un fallback.

Preferirei la switchdichiarazione per i percorsi di codice statico e la Dictionaryper i percorsi di codice dinamico (in cui è possibile aggiungere o rimuovere voci). Il compilatore potrebbe essere in grado di eseguire alcune ottimizzazioni statiche con switchciò che non può con Dictionary.

È interessante notare che questo Dictionarymodello è ciò che le persone a volte fanno in Python, perché a Python manca l' switchaffermazione. Altrimenti usano catene if-else.


1

In generale, non preferirei nessuno dei due.

Qualunque cosa stia consumando questo dovrebbe funzionare con a Func<int, IBigObject>. Quindi la fonte della tua mappatura può essere un Dizionario o un metodo che ha un'istruzione switch o una chiamata al servizio web o una ricerca di file ... qualunque cosa.

Per quanto riguarda l'implementazione, preferirei il dizionario dal momento che è più facilmente rifattorizzato da "dizionario hard code, chiave di ricerca, restituisce il risultato" a "carica dizionario da file, chiave di ricerca, restituisce risultato".

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.