È necessario dichiarare metodi utilizzando overload o parametri facoltativi in ​​C # 4.0?


93

Stavo guardando il discorso di Anders su C # 4.0 e l'anteprima di C # 5.0 , e mi ha fatto pensare a quando i parametri opzionali sono disponibili in C # quale sarà il modo consigliato per dichiarare metodi che non richiedono tutti i parametri specificati?

Ad esempio, qualcosa come la FileStreamclasse ha circa quindici costruttori diversi che possono essere suddivisi in "famiglie" logiche, ad esempio quelli sotto di una stringa, quelli di an IntPtre quelli di a SafeFileHandle.

FileStream(string,FileMode);
FileStream(string,FileMode,FileAccess);
FileStream(string,FileMode,FileAccess,FileShare);
FileStream(string,FileMode,FileAccess,FileShare,int);
FileStream(string,FileMode,FileAccess,FileShare,int,bool);

Mi sembra che questo tipo di pattern potrebbe essere semplificato avendo invece tre costruttori e usando parametri opzionali per quelli che possono essere impostati come predefiniti, il che renderebbe le diverse famiglie di costruttori più distinte [nota: so che questo cambiamento non sarà made in BCL, sto parlando ipoteticamente per questo tipo di situazione].

Cosa ne pensi? Da C # 4.0 avrà più senso rendere i gruppi strettamente correlati di costruttori e metodi un unico metodo con parametri opzionali, o c'è una buona ragione per attenersi al tradizionale meccanismo di molti sovraccarichi?

Risposte:


120

Considererei quanto segue:

  • Hai bisogno che il tuo codice venga utilizzato da linguaggi che non supportano parametri opzionali? In tal caso, considerare di includere i sovraccarichi.
  • Hai membri nel tuo team che si oppongono violentemente ai parametri opzionali? (A volte è più facile convivere con una decisione che non ti piace che discutere il caso.)
  • Sei sicuro che le tue impostazioni predefinite non cambieranno tra le build del tuo codice o, se lo possono fare, i tuoi chiamanti saranno d'accordo?

Non ho verificato come funzioneranno i valori predefiniti, ma presumo che i valori predefiniti verranno inseriti nel codice chiamante, in modo molto simile ai riferimenti ai constcampi. Di solito va bene - le modifiche a un valore predefinito sono comunque piuttosto significative - ma queste sono le cose da considerare.


20
+1 per la saggezza sul pragmatismo: a volte è più facile convivere con una decisione che non ti piace che discutere il caso.
legends2k

13
@romkyns: No, l'effetto degli overload non è la stessa cosa con il punto 3. Con gli overload che forniscono i valori predefiniti, i valori predefiniti sono all'interno del codice della libreria , quindi se modifichi il valore predefinito e fornisci una nuova versione della libreria, i chiamanti lo faranno vedere il nuovo predefinito senza ricompilazione. Mentre con i parametri opzionali, dovresti ricompilare per "vedere" i nuovi valori predefiniti. Molte volte non è una distinzione importante, ma è una distinzione.
Jon Skeet

ciao @ JonSkeet, vorrei sapere se usiamo sia la funzione ie con paramater opzionale che l'altra con sovraccarico quale metodo verrà chiamato ?? es. Aggiungi (int a, int b) e Aggiungi (int a, int b, int c = 0) e la chiamata di funzione dice: Add (5,10); quale metodo sarà chiamato funzione di sovraccarico o funzione di parametro opzionale? grazie :)
SHEKHAR SHETE

@ Shekshar: l'hai provato? Leggi le specifiche per i dettagli, ma fondamentalmente in un tie-break vince un metodo in cui il compilatore non ha dovuto inserire alcun parametro facoltativo.
Jon Skeet,

@JonSkeet solo ora ho provato con sopra ... il sovraccarico della funzione vince sul parametro opzionale :)
SHEKHAR SHETE

19

Quando un overload del metodo normalmente esegue la stessa cosa con un numero diverso di argomenti, verranno utilizzati i valori predefiniti.

Quando un overload del metodo esegue una funzione in modo diverso in base ai suoi parametri, continuerà a essere utilizzato l'overloading.

Ho usato l'opzionale nei miei giorni VB6 e da allora l'ho perso, ridurrà un sacco di duplicazioni di commenti XML in C #.


11

Sto usando Delphi con parametri opzionali da sempre. Sono invece passato a usare i sovraccarichi.

Perché quando crei più sovraccarichi, entrerai invariabilmente in conflitto con un modulo di parametro facoltativo, e quindi dovrai convertirli comunque in non facoltativi.

E mi piace l'idea che generalmente ci sia un super metodo, e il resto sono involucri più semplici attorno a quello.


1
Sono molto d'accordo con questo, tuttavia c'è l'avvertenza che quando hai un metodo che accetta più (3+) parametri, che sono per natura tutti "opzionali" (potrebbero essere sostituiti con un valore predefinito) potresti finire con molte permutazioni della firma del metodo per non beneficiare più. Si consideri Foo(A, B, C)richiede Foo(A), Foo(B), Foo(C), Foo(A, B), Foo(A, C), Foo(B, C).
Dan Lugg

7

Userò sicuramente la funzione dei parametri opzionali di 4.0. Si sbarazza del ridicolo ...

public void M1( string foo, string bar )
{
   // do that thang
}

public void M1( string foo )
{
  M1( foo, "bar default" ); // I have always hated this line of code specifically
}

... e inserisce i valori proprio dove il chiamante può vederli ...

public void M1( string foo, string bar = "bar default" )
{
   // do that thang
}

Molto più semplice e molto meno soggetto a errori. In realtà l'ho visto come un bug nel caso di sovraccarico ...

public void M1( string foo )
{
   M2( foo, "bar default" );  // oops!  I meant M1!
}

Non ho ancora giocato con il compilatore 4.0, ma non sarei scioccato nell'apprendere che il compilatore emette semplicemente i sovraccarichi per te.


6

I parametri facoltativi sono essenzialmente una parte di metadati che indirizza un compilatore che sta elaborando una chiamata al metodo per inserire i valori predefiniti appropriati nel sito della chiamata. Al contrario, gli overload forniscono un mezzo tramite il quale un compilatore può selezionare uno dei numerosi metodi, alcuni dei quali potrebbero fornire essi stessi valori predefiniti. Si noti che se si prova a chiamare un metodo che specifica parametri opzionali da codice scritto in un linguaggio che non li supporta, il compilatore richiederà che siano specificati i parametri "opzionali", ma poiché chiamare un metodo senza specificare un parametro opzionale è equivalente a chiamarlo con un parametro uguale al valore predefinito, non ci sono ostacoli a tali linguaggi che chiamano tali metodi.

Una conseguenza significativa dell'associazione di parametri facoltativi nel sito della chiamata è che verranno assegnati valori in base alla versione del codice di destinazione disponibile per il compilatore. Se un assembly Fooha un metodo Boo(int)con un valore predefinito di 5 e l'assembly Barcontiene una chiamata a Foo.Boo(), il compilatore lo elaborerà come file Foo.Boo(5). Se il valore predefinito viene modificato in 6 e l'assembly Fooviene ricompilato, Barcontinuerà a chiamare a Foo.Boo(5)meno che o fino a quando non viene ricompilato con quella nuova versione di Foo. Si dovrebbe quindi evitare di utilizzare parametri opzionali per cose che potrebbero cambiare.


Ri: "Si dovrebbe quindi evitare di utilizzare parametri opzionali per cose che potrebbero cambiare." Sono d'accordo che questo può essere problematico se la modifica passa inosservata dal codice client. Tuttavia, lo stesso problema esiste quando il valore di default è nascosto all'interno di un sovraccarico di metodo: void Foo(int value) … void Foo() { Foo(42); }. Dall'esterno, il chiamante non sa quale valore predefinito viene utilizzato, né quando potrebbe cambiare; si dovrebbe monitorare la documentazione scritta per questo. I valori predefiniti per i parametri opzionali possono essere visti proprio come questo: documentazione nel codice qual è il valore predefinito.
stakx - non contribuisce più il

@stakx: se un overload senza parametri si concatena a un overload con un parametro, la modifica del valore "predefinito" di quel parametro e la ricompilazione della definizione dell'overload cambierà il valore che utilizza anche se il codice chiamante non viene ricompilato .
supercat

Vero ma questo non lo rende più problematico dell'alternativa. In un caso (sovraccarico del metodo), il codice chiamante non ha voce in capitolo nel valore predefinito. Ciò può essere appropriato se il codice chiamante non si interessa affatto del parametro opzionale e di cosa significa. Nell'altro caso (parametro opzionale con valore predefinito), il codice chiamante compilato in precedenza non sarà influenzato quando il valore predefinito cambia. Ciò può anche essere appropriato quando il codice chiamante si preoccupa effettivamente del parametro; ometterlo nella sorgente è come dire "il valore predefinito attualmente suggerito va bene per me".
stakx - non contribuisce più il

Il punto che sto cercando di sottolineare qui è che, sebbene ci siano conseguenze per entrambi gli approcci (come hai sottolineato), non sono intrinsecamente vantaggiosi o svantaggiosi. Ciò dipende anche dalle esigenze e dagli obiettivi del codice chiamante. Da quel punto di vista, ho trovato il verdetto nell'ultima frase della tua risposta un po 'troppo rigido.
stakx - non contribuisce più il

@stakx: ho detto "evitare di usare" invece di "non usare mai". Se cambiare X significherà che la prossima ricompilazione di Y cambierà il comportamento di Y, ciò richiederà che un sistema di compilazione sia configurato in modo che ogni ricompilazione di X ricompili anche Y (rallentando le cose), o creerà il rischio che un programmatore cambi X in un modo che interromperà Y la prossima volta che viene compilato e scoprirà tale rottura solo in un secondo momento quando Y viene modificato per qualche motivo totalmente indipendente. I parametri predefiniti dovrebbero essere utilizzati solo quando i loro vantaggi superano tali costi.
supercat

4

Si può discutere se gli argomenti opzionali o i sovraccarichi debbano essere usati o meno, ma soprattutto, ognuno ha la propria area in cui sono insostituibili.

Gli argomenti facoltativi, se utilizzati in combinazione con argomenti denominati, sono estremamente utili se combinati con alcuni elenchi di argomenti lunghi con tutti gli optional delle chiamate COM.

I sovraccarichi sono estremamente utili quando il metodo è in grado di operare su molti tipi di argomenti diversi (solo uno degli esempi), e fa casting internamente, per esempio; basta alimentarlo con qualsiasi tipo di dati che abbia senso (che è accettato da un sovraccarico esistente). Non posso batterlo con argomenti opzionali.


3

Non vedo l'ora di parametri opzionali perché mantiene ciò che i valori predefiniti sono più vicini al metodo. Quindi, invece di dozzine di righe per gli overload che chiamano semplicemente il metodo "espanso", definisci semplicemente il metodo una volta e puoi vedere a cosa servono i parametri opzionali per impostazione predefinita nella firma del metodo. Preferisco guardare:

public Rectangle (Point start = Point.Zero, int width, int height)
{
    Start = start;
    Width = width;
    Height = height;
}

Invece di questo:

public Rectangle (Point start, int width, int height)
{
    Start = start;
    Width = width;
    Height = height;
}

public Rectangle (int width, int height) :
    this (Point.Zero, width, height)
{
}

Ovviamente questo esempio è davvero semplice ma nel caso dell'OP con 5 sovraccarichi, le cose possono diventare molto veloci.


7
Ho sentito che i parametri opzionali dovrebbero essere gli ultimi, no?
Ilya Ryzhenkov

Dipende dal tuo design. Forse l'argomento "start" è normalmente importante, tranne quando non lo è. Forse hai la stessa firma da qualche altra parte, il che significa qualcosa di diverso. Per un esempio artificioso, public Rectangle (int width, int height, Point innerSquareStart, Point innerSquareEnd) {}
Robert P

13
Da quello che hanno detto nel discorso, i parametri opzionali devono essere dopo i parametri obbligatori.
Greg Beech

3

Uno dei miei aspetti preferiti dei parametri opzionali è che vedi cosa succede ai tuoi parametri se non li fornisci, anche senza passare alla definizione del metodo. Visual Studio ti mostrerà semplicemente il valore predefinito per il parametro quando digiti il ​​nome del metodo. Con un metodo di sovraccarico sei bloccato con la lettura della documentazione (se disponibile) o con la navigazione diretta alla definizione del metodo (se disponibile) e al metodo che l'overload racchiude.

In particolare: lo sforzo di documentazione può aumentare rapidamente con la quantità di sovraccarichi e probabilmente finirai per copiare i commenti già esistenti dai sovraccarichi esistenti. Questo è abbastanza fastidioso, poiché non produce alcun valore e infrange il principio DRY ). D'altra parte, con un parametro opzionale c'è esattamente un posto in cui tutti i parametri sono documentati e durante la digitazione puoi vedere il loro significato così come i loro valori predefiniti .

Ultimo ma non meno importante, se sei il consumatore di un'API potresti non avere nemmeno la possibilità di ispezionare i dettagli di implementazione (se non hai il codice sorgente) e quindi non avere alcuna possibilità di vedere a quale super metodo quelli sovraccaricati stanno avvolgendo. Quindi sei bloccato nella lettura del documento e nella speranza che tutti i valori predefiniti siano elencati lì, ma non è sempre così.

Naturalmente, questa non è una risposta che gestisce tutti gli aspetti, ma penso che ne aggiunga una che non è stata trattata finora.


1

Sebbene siano (presumibilmente?) Due modi concettualmente equivalenti disponibili per modellare la tua API da zero, sfortunatamente hanno qualche sottile differenza quando devi considerare la compatibilità con le versioni precedenti del runtime per i tuoi vecchi client in natura. Il mio collega (grazie Brent!) Mi ha indicato questo meraviglioso post: Problemi di controllo delle versioni con argomenti opzionali . Alcune citazioni da esso:

Il motivo per cui i parametri facoltativi sono stati introdotti in C # 4 in primo luogo era supportare l'interoperabilità COM. Questo è tutto. E ora stiamo imparando tutte le implicazioni di questo fatto. Se si dispone di un metodo con parametri opzionali, non è mai possibile aggiungere un sovraccarico con parametri opzionali aggiuntivi per paura di causare una modifica significativa in fase di compilazione. E non è mai possibile rimuovere un sovraccarico esistente, poiché si è sempre trattato di un cambiamento radicale in fase di esecuzione. Hai praticamente bisogno di trattarlo come un'interfaccia. La tua unica possibilità in questo caso è scrivere un nuovo metodo con un nuovo nome. Quindi tieni presente questo se prevedi di utilizzare argomenti opzionali nelle tue API.


1

Un avvertimento sui parametri facoltativi è il controllo delle versioni, in cui un refactoring ha conseguenze non intenzionali. Un esempio:

Codice iniziale

public string HandleError(string message, bool silent=true, bool isCritical=true)
{
  ...
}

Supponiamo che questo sia uno dei tanti chiamanti del metodo sopra:

HandleError("Disk is full", false);

Qui l'evento non tace ed è trattato come critico.

Ora diciamo che dopo un refactoring troviamo che tutti gli errori richiedono comunque all'utente, quindi non abbiamo più bisogno del flag silenzioso. Quindi lo rimuoviamo.

Dopo il refactoring

La prima chiamata viene comunque compilata e diciamo che passa attraverso il refactor invariato:

public string HandleError(string message, /*bool silent=true,*/ bool isCritical=true)
{
  ...
}

...

// Some other distant code file:
HandleError("Disk is full", false);

Ora falseavrà un effetto indesiderato, l'evento non sarà più considerato critico.

Ciò potrebbe causare un sottile difetto, poiché non ci sarà alcun errore di compilazione o di runtime (a differenza di altri avvertimenti di optional, come questo o questo ).

Nota che ci sono molte forme di questo stesso problema. Un'altra forma è delineata qui .

Si noti, inoltre, che rigorosamente utilizzando parametri denominati quando si chiama il metodo eviterà il problema, come ad esempio in questo modo: HandleError("Disk is full", silent:false). Tuttavia, potrebbe non essere pratico presumere che tutti gli altri sviluppatori (o utenti di un'API pubblica) lo faranno.

Per questi motivi eviterei di utilizzare parametri opzionali in un'API pubblica (o anche un metodo pubblico se potrebbe essere ampiamente utilizzato) a meno che non ci siano altre considerazioni convincenti.


0

Entrambi i parametri opzionali, il sovraccarico del metodo hanno vantaggi o svantaggi. Dipende dalla preferenza per scegliere tra di loro.

Parametro opzionale: disponibile solo in .Net 4.0. parametro opzionale riduce la dimensione del codice. Non è possibile definire out e ref parametro

metodi sovraccaricati: è possibile definire i parametri Out e ref. La dimensione del codice aumenterà ma i metodi sovraccaricati sono facili da capire.


0

In molti casi vengono utilizzati parametri opzionali per cambiare l'esecuzione. Per esempio:

decimal GetPrice(string productName, decimal discountPercentage = 0)
{

    decimal basePrice = CalculateBasePrice(productName);

    if (discountPercentage > 0)
        return basePrice * (1 - discountPercentage / 100);
    else
        return basePrice;
}

Il parametro di sconto qui viene utilizzato per alimentare l'istruzione if-then-else. C'è il polimorfismo che non è stato riconosciuto, e quindi è stato implementato come un'istruzione if-then-else. In questi casi, è molto meglio suddividere i due flussi di controllo in due metodi indipendenti:

decimal GetPrice(string productName)
{
    decimal basePrice = CalculateBasePrice(productName);
    return basePrice;
}

decimal GetPrice(string productName, decimal discountPercentage)
{

    if (discountPercentage <= 0)
        throw new ArgumentException();

    decimal basePrice = GetPrice(productName);

    decimal discountedPrice = basePrice * (1 - discountPercentage / 100);

    return discountedPrice;

}

In questo modo abbiamo persino protetto la classe dal ricevere una chiamata con sconto zero. Quella chiamata significherebbe che il chiamante pensa che ci sia lo sconto, ma in realtà non c'è alcun sconto. Tale malinteso può facilmente causare un bug.

In casi come questo, preferisco non avere parametri opzionali, ma per forzare il chiamante a selezionare esplicitamente lo scenario di esecuzione che si adatta alla sua situazione attuale.

La situazione è molto simile ad avere parametri che possono essere nulli. Questa è altrettanto una cattiva idea quando l'implementazione si riduce a dichiarazioni come if (x == null).

È possibile trovare un'analisi dettagliata su questi collegamenti: Evitare parametri opzionali ed evitare parametri nulli


0

Per aggiungere un gioco da ragazzi quando usare un sovraccarico invece degli optional:

Ogni volta che hai un numero di parametri che hanno senso solo insieme, non introdurre optional su di essi.

O più in generale, ogni volta che le firme del tuo metodo abilitano modelli di utilizzo che non hanno senso, limita il numero di permutazioni delle possibili chiamate. Ad esempio, usando gli overload invece degli optionals (questa regola vale anche quando hai diversi parametri dello stesso tipo di dati, tra l'altro; qui, dispositivi come metodi di fabbrica o tipi di dati personalizzati possono aiutare).

Esempio:

enum Match {
    Regex,
    Wildcard,
    ContainsString,
}

// Don't: This way, Enumerate() can be called in a way
//         which does not make sense:
IEnumerable<string> Enumerate(string searchPattern = null,
                              Match match = Match.Regex,
                              SearchOption searchOption = SearchOption.TopDirectoryOnly);

// Better: Provide only overloads which cannot be mis-used:
IEnumerable<string> Enumerate(SearchOption searchOption = SearchOption.TopDirectoryOnly);
IEnumerable<string> Enumerate(string searchPattern, Match match,
                              SearchOption searchOption = SearchOption.TopDirectoryOnly);
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.