Outras respostas deram grandes explicações sobre por que um parâmetro opcional não pode ser uma expressão dinâmica. Mas, para recontar, os parâmetros padrão se comportam como constantes de tempo de compilação. Isso significa que o compilador deve ser capaz de avaliá-los e apresentar uma resposta. Existem pessoas que desejam que o C # adicione suporte ao compilador que avalia expressões dinâmicas ao encontrar declarações constantes - esse tipo de recurso estaria relacionado aos métodos de marcação como "puros", mas isso não é uma realidade agora e talvez nunca seja.
Uma alternativa para usar um parâmetro padrão C # para esse método seria usar o padrão exemplificado por XmlReaderSettings
. Nesse padrão, defina uma classe com um construtor sem parâmetros e propriedades publicamente graváveis. Em seguida, substitua todas as opções pelos padrões do seu método por um objeto desse tipo. Mesmo torne esse objeto opcional, especificando um padrão null
para ele. Por exemplo:
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);
}
}
Para chamar, use essa sintaxe estranha para instanciar e atribuir propriedades em uma única expressão:
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
Desvantagens
Essa é uma abordagem realmente pesada para resolver esse problema. Se você estiver escrevendo uma interface interna rápida e suja e tornando o valor TimeSpan
nulo e o tratamento nulo como o valor padrão desejado funcionaria bem, faça isso.
Além disso, se você tiver um grande número de parâmetros ou estiver chamando o método em um loop restrito, isso terá a sobrecarga das instanciações de classe. Obviamente, se chamar esse método em um loop apertado, pode ser natural e até muito fácil reutilizar uma instância do FooSettings
objeto.
Benefícios
Como mencionei no comentário no exemplo, acho que esse padrão é ótimo para APIs públicas. Adicionar novas propriedades a uma classe é uma alteração ABI ininterrupta, portanto, você pode adicionar novos parâmetros opcionais sem alterar a assinatura do seu método usando esse padrão - fornecendo mais opções ao código compilado mais recentemente, continuando a suportar o código compilado antigo sem trabalho extra .
Além disso, como os parâmetros de método padrão integrados do C # são tratados como constantes de compilação e inseridos no site de chamada, os parâmetros padrão serão usados apenas pelo código quando forem recompilados. Ao instanciar um objeto de configurações, o chamador carrega dinamicamente os valores padrão ao chamar seu método. Isso significa que você pode atualizar os padrões apenas alterando sua classe de configurações. Portanto, esse padrão permite alterar os valores padrão sem a necessidade de recompilar os chamadores para ver os novos valores, se desejado.
new TimeSpan(2000)
isso não significa 2000 milissegundos, significa 2000 "ticks", que são 0,2 milissegundos ou um 10.000ésimo de dois segundos.