2011-09-22 9 views
5

Ho bisogno di prendere un Widget che ha già diversi valori di proprietà impostati. Devo cambiare il nome del Widget. Sono attratto dall'opzione 3, ma mi viene difficile articolare il perché.Come meglio modificare e restituire un argomento C#?

public void Do(Widget widget) { // 1 
    widget.Name = "new name"; 
} 

public void Do(ref Widget widget) { // 2 
    widget.Name = "new name"; 
} 

public Widget Do(Widget widget) { // 3 
    widget.Name = "new name"; 
    return widget; 
} 

Mi piacerebbe giocare avvocato del diavolo, con un paio di domande e raccogliere le risposte, per aiutarmi a spiegare perché mi attira Opzione 3.

Opzione 1: Perché non solo modificare il Widget che è passato? Stai solo "restituendo" un oggetto. Perché non usare solo l'oggetto che è passato?

Opzione 2: Perché non ritorno vuoto? Perché non comunicare semplicemente nella firma che utilizzerai il puntatore di memoria effettivo sull'oggetto parametro stesso?

Opzione 3: Non è strano per te che stai restituendo lo stesso oggetto in cui stai passando?

+0

È difficile avere una buona opinione senza più contesto. – Cymen

+0

@Lance: la formulazione di domande, esempi e commenti su ciascuna opzione suggerisce una confusione sul comportamento dei tipi di riferimento in .NET. Per esempio, l'uso di 'ref' nella seconda opzione è del tutto superfluo. – LBushkin

+1

@LBushkin Concordato. La mia comprensione è che l'Opzione 2 sarebbe molto più interessante se mi assegnassi a 'widget', non a una delle sue proprietà, ma penso che l'Opzione 2, come viene mostrato, rifletta la confusione che molti sviluppatori (a volte forse includo me stesso) avere l'uso corretto della parola chiave ref. – lance

risposta

12

Opzione 1: Questo è l'approccio più comune - non è necessario ref se non si desidera modificare il riferimento stesso. Assicurarsi di denominare il metodo in modo appropriato in modo che l'aspettativa è che l'oggetto passato viene effettivamente modificato.

Opzione 2: Questo è utile solo se si desidera cambiare il riferimento passato in sé, cioè creare una nuova Widget istanza o impostare il riferimento per puntare a un widget esistente (questo potrebbe essere utile se si desidera mantenere basso il numero complessivo di istanze se tutte hanno le stesse proprietà, vedere Flyweight pattern, in generale i widget restituiti dovrebbero essere immutabili in questo caso e si utilizzerà invece una factory). Nel tuo caso questo non sembra appropriato.

Opzione 3: Questo consente un approccio fluente "builder" - ha anche i suoi vantaggi, cioè il concatenamento di modifiche al Widget, che alcuni considerano più espressivo e di auto-documentazione. Vedi anche Fluent interface

+0

Sebbene il chiamante non specifichi, è possibile che il widget sia una struttura. In tal caso, solo le opzioni 2 e 3 avrebbero senso. – LBushkin

+0

@LBushkin: È vero, lo trovo altamente improbabile, in tal caso probabilmente ha altri problemi. – BrokenGlass

+0

@BrokenGlass Quale convenzione di denominazione raccomanderesti per impostare l'aspettativa che l'oggetto passato sia stato modificato? – lance

4

Non c'è in realtà alcuna differenza funzionale tra le 3 opzioni. (Ci sono differenze, proprio nessuno che sono rilevanti per la tua domanda.) Keep it simple - uso l'opzione 1.

6

Opzione 1:

fine. Esattamente quello che userei. È possibile modificare il contenuto del widget, ma non il widget stesso.

Opzione 2:

No. No. No. di non modificare il riferimento stesso, in modo non è necessario utilizzare ref. Se si è modificato il riferimento stesso (ad esempio widget = new Widget() poi out/ref sono le scelte corrette, ma nella mia esperienza questo è raramente necessaria

Opzione 3:.
Simile a l'opzione 1. Ma possono essere concatenati in un fluente API di stile Personalmente non mi piace.Io uso questa firma solo se restituisco una copia e non intatto l'oggetto originale.


Ma la cosa più importante è come si nome il metodo. Il nome deve implicare chiaramente che l'oggetto originale è mutato.

In molte situazioni opterei per l'opzione 4: rendere il tipo immutabile e restituire una copia. Ma con i widget che ovviamente sono entità e non valore, questo non ha senso.

+0

L'opzione 1 non modificherà il widget nel codice che chiama questa funzione perché viene passato per valore non per riferimento – David

+2

@David Se il widget è un tipo di riferimento, modificherà il contenuto del widget. Quello che dici si applica solo a struct/value-types, e qualcuno che fa di un widget un tipo di valore in C# è pazzo. – CodesInChaos

+0

@David: ** La tua affermazione sarebbe vera solo se 'Widget' è una struct **. Se si tratta di un tipo di riferimento (come generalmente si assume), un riferimento all'istanza 'Widget' viene passato per valore, consentendo al chiamante di osservare le modifiche apportate nel metodo' Do() '. – LBushkin

1

Penso che l'opzione 1 e l'opzione 3 siano entrambe opzioni valide. L'opzione 3 ha il vantaggio di essere autodocumentante in quanto implica che si sta modificando il widget nel metodo. Penso che l'opzione peggiore sia l'opzione 2. La parola chiave ref implica che stai modificando il riferimento dell'oggetto, che sicuramente non stai facendo.

+1

Non vedo l'opzione 3 come auto-documentante. Nella maggior parte dei casi questa firma implica che non si modifica il parametro, ma si restituisce una copia modificata. Il vantaggio dell'opzione 3 è che consente il concatenamento, ma penso che sia meno autodocumentante. – CodesInChaos

+0

@CodeInChaos - In pratica uso l'Opzione 1, ma posso vedere persone che capiscono che le proprietà sull'oggetto possono cambiare nell'Opzione 3, dove come potrebbero non aspettarselo nell'opzione 1. Ad esempio: 'public void Log (MyClass a) {a.IWasLogged = true; Logger.Log (a); } 'a è stato cambiato qui, ma potrebbe non essere evidente allo sviluppatore. Non sono sicuro che valga la pena optare per l'Opzione 3, ma ho voluto evidenziare questo fatto, suppongo. – skaz

Problemi correlati