Altre risposte hanno dato grandi spiegazioni sul perché un parametro opzionale non può essere un'espressione dinamica. Ma, per raccontare, i parametri predefiniti si comportano come costanti di tempo di compilazione. Ciò significa che il compilatore deve essere in grado di valutarli e trovare una risposta. Ci sono alcune persone che vogliono che C # aggiunga supporto per il compilatore che valuta espressioni dinamiche quando si incontrano dichiarazioni costanti: questo tipo di funzionalità sarebbe collegata ai metodi di marcatura "puri", ma al momento non è una realtà e potrebbe non esserlo mai.
Un'alternativa all'utilizzo di un parametro predefinito C # per tale metodo sarebbe quella di utilizzare il modello esemplificato da XmlReaderSettings
. In questo modello, definire una classe con un costruttore senza parametri e proprietà scrivibili pubblicamente. Quindi sostituire tutte le opzioni con i valori predefiniti nel metodo con un oggetto di questo tipo. Anche rendere questo oggetto facoltativo specificando un valore predefinito null
per esso. Per esempio:
public class FooSettings
{
public TimeSpan Span { get; set; } = TimeSpan.FromSeconds(2);
// I imagine that if you had a heavyweight default
// thing you’d want to avoid instantiating it right away
// because the caller might override that parameter. So, be
// lazy! (Or just directly store a factory lambda with Func<IThing>).
Lazy<IThing> thing = new Lazy<IThing>(() => new FatThing());
public IThing Thing
{
get { return thing.Value; }
set { thing = new Lazy<IThing>(() => value); }
}
// Another cool thing about this pattern is that you can
// add additional optional parameters in the future without
// even breaking ABI.
//bool FutureThing { get; set; } = true;
// You can even run very complicated code to populate properties
// if you cannot use a property initialization expression.
//public FooSettings() { }
}
public class Bar
{
public void Foo(FooSettings settings = null)
{
// Allow the caller to use *all* the defaults easily.
settings = settings ?? new FooSettings();
Console.WriteLine(settings.Span);
}
}
Per chiamare, usa quella strana sintassi per creare un'istanza e assegnare proprietà in un'unica espressione:
bar.Foo(); // 00:00:02
bar.Foo(new FooSettings { Span = TimeSpan.FromDays(1), }); // 1.00:00:00
bar.Foo(new FooSettings { Thing = new MyCustomThing(), }); // 00:00:02
Svantaggi
Questo è un approccio davvero pesante per risolvere questo problema. Se stai scrivendo un'interfaccia interna veloce e sporca e rendendo TimeSpan
nullable e trattando null come il tuo valore predefinito desiderato funzionerebbe bene, fallo invece.
Inoltre, se si dispone di un gran numero di parametri o si sta chiamando il metodo in un ciclo stretto, questo avrà il sovraccarico delle istanze di classe. Naturalmente, se si chiama un metodo simile in un ciclo stretto, potrebbe essere naturale e persino molto semplice riutilizzare un'istanza FooSettings
dell'oggetto.
Benefici
Come ho detto nel commento nell'esempio, penso che questo modello sia ottimo per le API pubbliche. L'aggiunta di nuove proprietà a una classe è una modifica ABI senza interruzioni, quindi è possibile aggiungere nuovi parametri opzionali senza modificare la firma del metodo utilizzando questo modello, offrendo più codice compilato più recentemente più opzioni continuando a supportare il vecchio codice compilato senza lavoro aggiuntivo .
Inoltre, poiché i parametri del metodo predefinito incorporato in C # sono trattati come costanti del compiletime e inseriti nel sito di chiamata, i parametri predefiniti verranno utilizzati dal codice solo dopo essere stati ricompilati. Creando un'istanza di un oggetto settings, il chiamante carica dinamicamente i valori predefiniti quando chiama il metodo. Ciò significa che è possibile aggiornare le impostazioni predefinite semplicemente modificando la classe delle impostazioni. Pertanto, questo modello consente di modificare i valori predefiniti senza dover ricompilare i chiamanti per visualizzare i nuovi valori, se desiderato.
new TimeSpan(2000)
non significa 2000 millisecondi, significa 2000 "tick" che è 0,2 millisecondi o un 10.000 di due secondi.