C # 4.0: Чи можна використовувати TimeSpan як необов'язковий параметр зі значенням за замовчуванням?


125

Обидва вони створюють помилку, кажучи, що вони повинні бути константа часу збирання:

void Foo(TimeSpan span = TimeSpan.FromSeconds(2.0))
void Foo(TimeSpan span = new TimeSpan(2000))

Перш за все, чи може хтось пояснити, чому ці значення неможливо визначити під час компіляції? І чи є спосіб вказати значення за замовчуванням для необов'язкового об'єкта TimeSpan?


11
Не пов'язане з тим, що ви просите, але пам’ятайте, що new TimeSpan(2000)це не означає 2000 мілісекунд, це означає 2000 «тиків», що становить 0,2 мілісекунди, або одну 10000-ту з двох секунд.
Jeppe Stig Nielsen

Відповіді:


173

Ви можете дуже легко обійти це питання, змінивши свій підпис.

void Foo(TimeSpan? span = null) {

   if (span == null) { span = TimeSpan.FromSeconds(2); }

   ...

}

Я повинен детальніше пояснити - причина цих виразів у вашому прикладі не є константами часу компіляції в тому, що під час компіляції компілятор не може просто виконати TimeSpan.FromSeconds (2.0) і вставити байти результату у свій компільований код.

Як приклад, розгляньте, чи намагалися ви використовувати DateTime.Now замість цього. Значення DateTime.Now змінюється кожного разу, коли воно виконується. Або припустимо, що TimeSpan.FromSeconds врахував гравітацію. Це абсурдний приклад, але правила констант часу компіляції не становлять особливих випадків лише тому, що ми знаємо, що TimeSpan.FromSeconds є детермінованим.


15
Тепер задокументуйте значення за замовчуванням у <param>, оскільки воно не видно в підписі.
Полковник Паніка

3
Я не можу цього зробити, я використовую спеціальне значення null для чогось іншого.
Полковник Паніка

4
@MattHickford - Тоді вам доведеться надати перевантажений метод або взяти за параметр мілісекунди.
Джош

19
Також можна використовувати span = span ?? TimeSpan.FromSeconds(2.0);з нульовим типом в тілі методу. Або var realSpan = span ?? TimeSpan.FromSeconds(2.0);отримати локальну змінну, яка не зводиться до нуля.
Jeppe Stig Nielsen

5
Що мені в цьому не подобається, це те, що вона передбачає для користувача функції, що ця функція "працює" з нульовим проміжком. Але це неправда! Null не є дійсним значенням для span, що стосується фактичної логіки функції. Мені б хотілося, щоб був кращий спосіб, який не здавався кодовим запахом ...
JoeCool

31

Моя спадщина VB6 викликає занепокоєння з ідеєю вважати "нульове значення" та "відсутнє значення" еквівалентом. У більшості випадків це, мабуть, добре, але у вас може виникнути ненавмисна побічна дія, або ви можете проковтнути винятковий стан (наприклад, якщо джерелоspan властивості або змінної, яке не повинно бути нульовим, але є).

Тому я б перевантажив метод:

void Foo()
{
    Foo(TimeSpan.FromSeconds(2.0));
}
void Foo(TimeSpan span)
{
    //...
}

1
+1 за цю чудову техніку. Параметри за замовчуванням дійсно повинні використовуватися лише з типами const. Інше, це ненадійно.
Лазло

2
Це підхід "пошановував час", що значення за замовчуванням замінені, і для цієї ситуації я думаю, що це найменш негарна відповідь;) Сам по собі він не обов'язково працює так добре для інтерфейсів, хоча ви дійсно хочете, щоб значення за замовчуванням у одне місце. У цьому випадку я виявив, що методи розширення є корисним інструментом: в інтерфейсі є один метод з усіма параметрами, потім ряд методів розширення, оголошених у статичному класі поряд з інтерфейсом, реалізують за замовчуванням різні перевантаження.
OlduwanSteve

23

Це добре працює:

void Foo(TimeSpan span = default(TimeSpan))


4
Ласкаво просимо до переповнення стека. Здається, ваша відповідь полягає в тому, що ви можете надати значення параметра за замовчуванням, якщо це одне дуже конкретне значення, яке дозволяє компілятор. Я зрозумів це правильно? (Ви можете відредагувати свою відповідь для уточнення.) Це буде кращою відповіддю, якби вона показала, як скористатися тим, що компілятор дозволяє дістатись до того, що запитував спочатку, який повинен був мати довільні інші TimeSpan значення, наприклад, задані new TimeSpan(2000).
Роб Кеннеді

2
Альтернативою, яка використовує якесь певне значення за замовчуванням, буде використання приватного статичного читання лише для TimeSpan defaultTimespan = Timespan.FromSeconds (2) у поєднанні з конструктором за замовчуванням та конструктором, що приймає часовий проміжок. public Foo (): це (за замовчуваннямTimespan) та public Foo (Timespan ts)
johan mårtensson

15

Набір значень, які можна використовувати як значення за замовчуванням, такі ж, як і для аргументу атрибута. Причина полягає в тому, що значення за замовчуванням кодуються в метадані всерединіDefaultParameterValueAttribute .

Щодо того, чому його не можна визначити під час компіляції. Набір значень та виразів над такими значеннями, дозволеними під час компіляції, перелічено в офіційній специфікації мови C # :

C # 6.0 - типи параметрів атрибутів :

Типи позиційних та іменованих параметрів для класу атрибутів обмежені типом параметрів атрибутів , якими є:

  • Один з таких типів: bool, byte, char, double, float, int, long, sbyte, short, string, uint, ulong, ushort.
  • Тип object.
  • Тип System.Type.
  • Перелічувальний тип.
    (за умови, що він має загальнодоступність і типи, в яких він вкладається (якщо він є) також має загальнодоступність)
  • Одновимірні масиви вищевказаних типів.

Тип TimeSpanне входить до жодного із цих списків, а тому не може використовуватися як константа.


2
Незначний вибір ниток: Виклик статичного методу не входить у жоден список. TimeSpanможе вмістити останній у цьому списку default(TimeSpan)є дійсним.
CodesInChaos

12
void Foo(TimeSpan span = default(TimeSpan))
{
    if (span == default(TimeSpan)) 
        span = TimeSpan.FromSeconds(2); 
}

надане default(TimeSpan)не є дійсним значенням для функції.

Або

//this works only for value types which TimeSpan is
void Foo(TimeSpan span = new TimeSpan())
{
    if (span == new TimeSpan()) 
        span = TimeSpan.FromSeconds(2); 
}

надане new TimeSpan()не є дійсним значенням.

Або

void Foo(TimeSpan? span = null)
{
    if (span == null) 
        span = TimeSpan.FromSeconds(2); 
}

Це має бути краще, враховуючи шанси, що nullзначення є дійсним значенням для функції, рідкісні.


4

TimeSpanє особливим випадком для DefaultValueAttributeі задається за допомогою будь-якої рядки, яку можна проаналізувати за допомогою TimeSpan.Parseметоду.

[DefaultValue("0:10:0")]
public TimeSpan Duration { get; set; }

3

Моя пропозиція:

void A( long spanInMs = 2000 )
{
    var ts = TimeSpan.FromMilliseconds(spanInMs);

    //...
}

BTW TimeSpan.FromSeconds(2.0)не дорівнює new TimeSpan(2000)- конструктор бере кліщі.


2

Інші відповіді чудово пояснюють, чому необов’язковий параметр не може бути динамічним виразом. Але, для перерахунку, параметри за замовчуванням поводяться як константи часу компіляції. Це означає, що компілятор повинен вміти їх оцінювати і придумати відповідь. Є деякі люди, які хочуть, щоб 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 # розглядаються як константи компіляції та вводяться на сайт виклику, параметри за замовчуванням будуть використовуватися кодом лише після його перекомпіляції. Ініціюючи об’єкт налаштувань, абонент динамічно завантажує значення за замовчуванням під час виклику вашого методу. Це означає, що ви можете оновити налаштування за замовчуванням, просто змінивши клас налаштувань. Таким чином, ця схема дозволяє змінювати значення за замовчуванням без необхідності перекомпілювати абонентів, щоб побачити нові значення, якщо це потрібно.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.