Інші відповіді чудово пояснюють, чому необов’язковий параметр не може бути динамічним виразом. Але, для перерахунку, параметри за замовчуванням поводяться як константи часу компіляції. Це означає, що компілятор повинен вміти їх оцінювати і придумати відповідь. Є деякі люди, які хочуть, щоб 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
Недоліки
Це дійсно важкий підхід до вирішення цієї проблеми. Якщо ви пишете швидкий і брудний внутрішній інтерфейс і робите TimeSpan
нульову обробку і обробляєте нуль, як потрібне значення за замовчуванням, буде добре працювати, зробіть це замість цього.
Крім того, якщо у вас є велика кількість параметрів або виклик методу в щільному циклі, це матиме накладні екземпляри класів. Звичайно, якщо викликати такий метод у жорсткому циклі, повторно використовувати екземпляр FooSettings
об'єкта може бути природним і навіть дуже просто .
Переваги
Як я згадував у коментарі в прикладі, я думаю, що ця модель чудово підходить для публічних API. Додавання нових властивостей до класу - це неперервна зміна ABI, тому ви можете додавати нові необов'язкові параметри, не змінюючи підписи свого методу за допомогою цього шаблону - надаючи нещодавно складеному коду більше варіантів, продовжуючи підтримувати старий скомпільований код без зайвої роботи .
Крім того, оскільки вбудовані в параметри методу за замовчуванням C # розглядаються як константи компіляції та вводяться на сайт виклику, параметри за замовчуванням будуть використовуватися кодом лише після його перекомпіляції. Ініціюючи об’єкт налаштувань, абонент динамічно завантажує значення за замовчуванням під час виклику вашого методу. Це означає, що ви можете оновити налаштування за замовчуванням, просто змінивши клас налаштувань. Таким чином, ця схема дозволяє змінювати значення за замовчуванням без необхідності перекомпілювати абонентів, щоб побачити нові значення, якщо це потрібно.
new TimeSpan(2000)
це не означає 2000 мілісекунд, це означає 2000 «тиків», що становить 0,2 мілісекунди, або одну 10000-ту з двох секунд.