Другие ответы дали отличные объяснения того, почему необязательный параметр не может быть динамическим выражением. Но, повторюсь, параметры по умолчанию ведут себя как константы времени компиляции. Это означает, что компилятор должен уметь их оценивать и давать ответ. Есть люди, которые хотят, чтобы в C # была добавлена поддержка компилятора, оценивающего динамические выражения при обнаружении константных объявлений - такого рода функция была бы связана с маркировкой методов «чистыми», но это не реальность прямо сейчас и, возможно, никогда не будет.
Альтернативой использованию параметра C # по умолчанию для такого метода было бы использование шаблона, представленного XmlReaderSettings
. В этом шаблоне определите класс с конструктором без параметров и общедоступными свойствами. Затем замените все параметры на значения по умолчанию в вашем методе с объектом этого типа. Даже сделайте этот объект необязательным, указав для него значение по умолчанию null
. Например:
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);
}
}
Чтобы вызвать, используйте этот странный синтаксис для создания и назначения свойств в одном выражении:
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
Downsides
Это действительно тяжелый подход к решению этой проблемы. Если вы пишете быстрый и грязный внутренний интерфейс и делаете TimeSpan
nullable и обрабатываете null, как желаемое значение по умолчанию, все будет работать нормально, сделайте это вместо этого.
Кроме того, если у вас есть большое количество параметров или вы вызываете метод в тесном цикле, это приведет к накладным расходам на создание экземпляров класса. Конечно, при вызове такого метода в замкнутом цикле было бы естественно и даже очень легко повторно использовать экземпляр FooSettings
объекта.
Преимущества
Как я упоминал в комментарии к примеру, я думаю, что этот шаблон отлично подходит для общедоступных API. Добавление новых свойств в класс - это неразрывное изменение ABI, поэтому вы можете добавлять новые необязательные параметры, не изменяя сигнатуру вашего метода, используя этот шаблон, предоставляя недавно скомпилированному коду дополнительные параметры, продолжая поддерживать старый скомпилированный код без дополнительной работы ,
Кроме того, поскольку встроенные в C # параметры метода по умолчанию обрабатываются как константы времени компиляции и запекаются в callite, параметры по умолчанию будут использоваться кодом только после его перекомпиляции. Создавая экземпляр объекта настроек, вызывающий объект динамически загружает значения по умолчанию при вызове вашего метода. Это означает, что вы можете обновить значения по умолчанию, просто изменив свой класс настроек. Таким образом, этот шаблон позволяет вам изменять значения по умолчанию без необходимости перекомпилировать вызывающие объекты, чтобы увидеть новые значения, если это необходимо.
new TimeSpan(2000)
виду, что это не означает 2000 миллисекунд, это означает 2000 «тактов», что составляет 0,2 миллисекунды или одну 10 000-ю из двух секунд.