Domanda

Stavo guardando i discorsi 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 raccomandato per dichiarare metodi che non richiedono tutti i parametri specificati?

Ad esempio qualcosa come la classe FileStream ha una quindicina di costruttori diversi che possono essere suddivisi in "famiglie" logiche, ad es. quelli sottostanti da una stringa, quelli da un IntPtr e quelli da un 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 modello possa essere semplificato disponendo invece di tre costruttori e utilizzando parametri opzionali per quelli che possono essere predefiniti, il che renderebbe le diverse famiglie di costruttori più distinte [nota: conosco questo cambiamento non sarà realizzato nel BCL, sto parlando ipoteticamente per questo tipo di situazione].

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

È stato utile?

Soluzione

Considererei quanto segue:

  • È necessario che il codice sia utilizzato da lingue che non supportano parametri opzionali? In tal caso, considerare l'inclusione dei sovraccarichi.
  • Hai membri nel tuo team che si oppongono violentemente a parametri opzionali? (A volte è più facile convivere con una decisione che non ti piace piuttosto che discutere il caso.)
  • Sei sicuro che i tuoi valori predefiniti non cambieranno tra le build del tuo codice, o se potrebbero, i tuoi chiamanti andranno bene?

Non ho verificato come funzioneranno le impostazioni predefinite, ma suppongo che i valori predefiniti verranno inseriti nel codice chiamante, più o meno come i riferimenti ai campi const . Di solito va bene - le modifiche a un valore predefinito sono comunque piuttosto significative - ma questi sono gli aspetti da considerare.

Altri suggerimenti

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

Quando un sovraccarico di metodo esegue una funzione in modo diverso in base ai suoi parametri, il sovraccarico continuerà a essere utilizzato.

Ho usato di nuovo facoltativo nei miei giorni VB6 e da allora non l'ho visto, ridurrà molto la duplicazione dei commenti XML in C #.

Uso Delphi, con parametri opzionali, per sempre. invece sono passato all'utilizzo dei sovraccarichi.

Perché quando vai a creare più sovraccarichi, invariabilmente ti confonderai con un modulo parametri opzionale; e dovrai quindi convertirli in non facoltativi comunque.

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

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 mette i valori esattamente 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 complier 4.0, ma non sarei scioccato nell'apprendere che il complier emette semplicemente i sovraccarichi per te.

I parametri opzionali sono essenzialmente un pezzo di metadati che indirizza un compilatore che elabora una chiamata del metodo per inserire valori predefiniti appropriati nel sito della chiamata. Al contrario, i sovraccarichi forniscono un mezzo attraverso il quale un compilatore può selezionare uno dei numerosi metodi, alcuni dei quali potrebbero fornire autonomamente i valori predefiniti. Nota che se si tenta di chiamare un metodo che specifica parametri opzionali dal codice scritto in una lingua che non li supporta, il compilatore richiederà che l'opzione "opzionale" parametri da specificare, ma poiché chiamare un metodo senza specificare un parametro facoltativo equivale a chiamarlo con un parametro uguale al valore predefinito, non vi sono ostacoli a tali lingue che chiamano tali metodi.

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

Non vedo l'ora di parametri opzionali perché mantiene ciò che le impostazioni predefinite sono più vicine al metodo. Quindi, invece di dozzine di linee per i sovraccarichi che chiamano semplicemente "espanso" metodo, è sufficiente definire il metodo una volta e si può vedere quali sono i parametri opzionali predefiniti nella firma del metodo. Preferirei 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 essere affollate molto velocemente.

Si può discutere se si debbano usare o meno argomenti opzionali o sovraccarichi, ma soprattutto, ognuno ha la propria area in cui è insostituibile.

Gli argomenti opzionali, se usati in combinazione con argomenti con nome, sono estremamente utili se combinati con alcuni elenchi di argomenti lunghi con tutte le opzioni di 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 esegue i casting internamente, ad esempio; lo inserisci semplicemente con qualsiasi tipo di dato che abbia senso (che è accettato da un sovraccarico esistente). Non posso batterlo con argomenti opzionali.

Uno dei miei aspetti preferiti dei parametri opzionali è che vedi cosa succede ai tuoi parametri se non li fornisci, anche senza andare alla definizione del metodo. Visual Studio mostrerà semplicemente il valore predefinito per il parametro quando si digita 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 avvolge il sovraccarico.

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, in quanto non produce alcun valore e rompe 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 vedrai il loro significato e i valori predefiniti .

Ultimo ma non meno importante, se sei il consumatore di un'API potresti non avere nemmeno la possibilità di ispezionare i dettagli dell'implementazione (se non hai il codice sorgente) e quindi non hai alcuna possibilità di vedere a quale super metodo quelli sovraccarichi si stanno avvolgendo. Quindi sei bloccato con la lettura del documento e la 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 uno che non è stato trattato finora.

Un avvertimento di parametri opzionali è il versioning, in cui un refactor 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 molti chiamanti del metodo sopra:

HandleError("Disk is full", false);

Qui l'evento non è silenzioso ed è considerato critico.

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

Dopo refactor

La prima chiamata si compila ancora, e diciamo che scivola invariato attraverso il refactor:

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

...

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

Ora false avrà un effetto indesiderato, l'evento non sarà più trattato come critico.

Ciò potrebbe comportare un sottile difetto, poiché non si verificherà alcun errore di compilazione o runtime (a differenza di altri avvertimenti sugli opzionali, come this o questo ).

Nota che ci sono molte forme di questo stesso problema. Un altro modulo è delineato qui .

Nota anche che l'uso rigoroso di parametri nominati quando chiami il metodo eviterà il problema, come questo: HandleError (" Il disco è pieno " ;, 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 usare parametri opzionali in un'API pubblica (o anche un metodo pubblico se potrebbe essere usato ampiamente) a meno che non vi siano altre considerazioni convincenti.

Entrambi i parametri opzionali, Metodo overload hanno il loro vantaggio o disadvantage.it dipende dalla tua preferenza di scegliere tra di loro.

Parametro opzionale: disponibile solo in .Net 4.0. parametro opzionale per ridurre le dimensioni del codice. Non è possibile definire il parametro out e ref

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

In molti casi vengono usati parametri opzionali per cambiare l'esecuzione. Ad 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 tali 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 dalla ricezione di una chiamata con zero sconti. Quella chiamata significherebbe che il chiamante pensa che ci sia lo sconto, ma in realtà non c'è nessuno sconto. Tale equivoco 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 adatto alla sua situazione attuale.

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

Puoi trovare un'analisi dettagliata su questi collegamenti: Evitare parametri opzionali e Evitare parametri null

Sebbene siano (presumibilmente?) due modi concettualmente equivalenti disponibili per modellare l'API da zero, sfortunatamente hanno qualche sottile differenza quando è necessario prendere in considerazione la compatibilità all'indietro del runtime per i vecchi client in natura. Il mio collega (grazie Brent!) Mi ha indicato questo post meraviglioso: problemi di versioning con argomenti opzionali . Qualche citazione da esso:

  

Il motivo per cui sono stati introdotti parametri opzionali in C # 4 in   il primo posto è stato quello di supportare l'interoperabilità COM. Questo è tutto. E ora, ri & # 8217; ri   conoscere le implicazioni complete di questo fatto. Se hai un   metodo con parametri opzionali, non è mai possibile aggiungere un sovraccarico con   parametri opzionali aggiuntivi per paura di causare un tempo di compilazione   cambiamento di rottura. E non puoi mai rimuovere un sovraccarico esistente, come   questo è sempre stato un cambiamento di runtime. Hai praticamente bisogno   per trattarlo come un'interfaccia. La tua unica risorsa in questo caso è   scrivere un nuovo metodo con un nuovo nome. Quindi sii consapevole di questo se hai intenzione di farlo   usa argomenti opzionali nelle tue API.

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 opzioni su di essi.

O più in generale, ogni volta che le firme del metodo consentono modelli di utilizzo che non hanno senso, limitare il numero di permutazioni di possibili chiamate. Ad esempio, usando sovraccarichi anziché opzionali (questa regola vale anche quando si hanno diversi parametri dello stesso tipo di dati, a proposito; 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);
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top