D'autres réponses ont donné de grandes explications sur les raisons pour lesquelles un paramètre facultatif ne peut pas être une expression dynamique. Mais, pour raconter, les paramètres par défaut se comportent comme des constantes de temps de compilation. Cela signifie que le compilateur doit être capable de les évaluer et de trouver une réponse. Il y a des gens qui veulent que C # ajoute la prise en charge du compilateur évaluant les expressions dynamiques lorsqu'il rencontre des déclarations constantes - ce type de fonctionnalité serait lié au marquage des méthodes «pures», mais ce n'est pas une réalité pour le moment et pourrait ne jamais l'être.
Une alternative à l'utilisation d'un paramètre par défaut C # pour une telle méthode serait d'utiliser le modèle illustré par XmlReaderSettings
. Dans ce modèle, définissez une classe avec un constructeur sans paramètre et des propriétés publiquement accessibles en écriture. Remplacez ensuite toutes les options par les valeurs par défaut de votre méthode par un objet de ce type. Rendez même cet objet facultatif en spécifiant une valeur par défaut de null
for it. Par exemple:
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);
}
}
Pour appeler, utilisez cette syntaxe étrange pour instancier et attribuer des propriétés dans une seule expression:
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
Inconvénients
Il s'agit d'une approche très lourde pour résoudre ce problème. Si vous écrivez une interface interne rapide et sale et que vous TimeSpan
rendez la valeur Nullable et que vous la traitez comme la valeur par défaut souhaitée, cela fonctionnerait bien, faites-le à la place.
De plus, si vous avez un grand nombre de paramètres ou si vous appelez la méthode dans une boucle serrée, cela entraînera la surcharge des instanciations de classe. Bien sûr, si vous appelez une telle méthode dans une boucle serrée, il peut être naturel et même très facile de réutiliser une instance de l' FooSettings
objet.
Avantages
Comme je l'ai mentionné dans le commentaire de l'exemple, je pense que ce modèle est idéal pour les API publiques. L'ajout de nouvelles propriétés à une classe est un changement ABI sans rupture, vous pouvez donc ajouter de nouveaux paramètres facultatifs sans changer la signature de votre méthode à l'aide de ce modèle - donnant plus d'options au code compilé plus récemment tout en continuant à prendre en charge l'ancien code compilé sans travail supplémentaire .
De plus, étant donné que les paramètres de méthode par défaut intégrés de C # sont traités comme des constantes de compilation et intégrés au site d'appel, les paramètres par défaut ne seront utilisés par le code qu'une fois recompilé. En instanciant un objet de paramètres, l'appelant charge dynamiquement les valeurs par défaut lors de l'appel de votre méthode. Cela signifie que vous pouvez mettre à jour les valeurs par défaut en modifiant simplement votre classe de paramètres. Ainsi, ce modèle vous permet de modifier les valeurs par défaut sans avoir à recompiler les appelants pour voir les nouvelles valeurs, si cela est souhaité.
new TimeSpan(2000)
cela ne signifie pas 2000 millisecondes, cela signifie 2000 "ticks", soit 0,2 millisecondes, soit un 10 000 ème de deux secondes.