C # 4.0: Могу ли я использовать TimeSpan в качестве необязательного параметра со значением по умолчанию? - PullRequest
111 голосов
/ 30 января 2010

Оба они генерируют ошибку, говоря, что они должны быть константой времени компиляции:

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

Прежде всего, кто-то может объяснить, почему эти значения не могут быть определены во время компиляции? И есть ли способ указать значение по умолчанию для необязательного объекта TimeSpan?

Ответы [ 8 ]

157 голосов
/ 30 января 2010

Вы можете обойти это очень легко, изменив свою подпись.

void Foo(TimeSpan? span = null) {

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

   ...

}

Я должен уточнить - причина того, что эти выражения в вашем примере не являются константами времени компиляции, заключается в том, что во время компиляции компилятор не может просто выполнить TimeSpan.FromSeconds (2.0) и вставить байты результата в ваш скомпилированный код .

В качестве примера рассмотрим, пытались ли вы использовать DateTime.Now вместо этого. Значение DateTime.Now изменяется каждый раз, когда оно выполняется. Или предположим, что TimeSpan.FromSeconds учитывает гравитацию. Это абсурдный пример, но правила констант времени компиляции не делают особых случаев только потому, что мы знаем, что TimeSpan.FromSeconds является детерминированным.

27 голосов
/ 29 декабря 2010

Мое наследие VB6 заставляет меня чувствовать себя неловко из-за того, что «нулевое значение» и «отсутствующее значение» считаются эквивалентными. В большинстве случаев это, вероятно, хорошо, но у вас может быть непреднамеренный побочный эффект, или вы можете проглотить исключительное условие (например, если источником span является свойство или переменная, которая не должна быть нулевой, но есть).

Поэтому я бы перегрузил метод:

void Foo()
{
    Foo(TimeSpan.FromSeconds(2.0));
}
void Foo(TimeSpan span)
{
    //...
}
19 голосов
/ 19 декабря 2012

Это прекрасно работает:

void Foo(TimeSpan span = default(TimeSpan))

13 голосов
/ 30 января 2010

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

Относительно того, почему это не может быть определено во время компиляции. Набор значений и выражений для таких значений, разрешенных во время компиляции, указан в официальной спецификации языка C # :

C # 6.0 - Типы параметров атрибута :

Типы позиционных и именованных параметров для класса атрибута ограничены типами параметров атрибута , а именно:

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

Тип TimeSpan не вписывается ни в один из этих списков и, следовательно, не может использоваться как константа.

11 голосов
/ 16 мая 2013
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 голосов
/ 19 июня 2012

TimeSpan является особым случаем для DefaultValueAttribute и указывается с использованием любой строки, которую можно проанализировать с помощью метода TimeSpan.Parse.

[DefaultValue("0:10:0")]
public TimeSpan Duration { get; set; }
3 голосов
/ 19 ноября 2016

Мое предложение:

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

    //...
}

КСТАТИ TimeSpan.FromSeconds(2.0) не равно new TimeSpan(2000) - конструктор принимает тики.

2 голосов
/ 08 марта 2016

Другие ответы дали большие объяснения того, почему необязательный параметр не может быть динамическим выражением. Но, чтобы пересчитать, параметры по умолчанию ведут себя как константы времени компиляции. Это означает, что компилятор должен уметь оценивать их и предлагать ответ. Есть люди, которые хотят, чтобы C # добавлял поддержку компилятора, оценивающего динамические выражения, при обнаружении константных объявлений - такая функция была бы связана с маркировкой методов «pure», но сейчас это не является реальностью и никогда не может быть *. 1003 *

Одной альтернативой использованию параметра 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 и рассматриваете ноль как желаемое значение по умолчанию , то будет работать нормально.

Кроме того, если у вас есть большое количество параметров или вы вызываете метод в узком цикле, это приведет к дополнительным затратам на создание экземпляров классов. Конечно, при вызове такого метода в узком цикле может быть естественным и даже очень легко повторно использовать экземпляр объекта FooSettings.

Преимущества

Как я уже упоминал в комментарии к примеру, я думаю, что этот шаблон отлично подходит для публичных API. Добавление новых свойств в класс - это непрерывное изменение ABI, поэтому вы можете добавлять новые необязательные параметры без изменения сигнатуры вашего метода, используя этот шаблон - предоставляя более недавно скомпилированному коду больше возможностей, продолжая при этом поддерживать старый скомпилированный код без дополнительной работы .

Кроме того, поскольку встроенные в C # параметры метода по умолчанию обрабатываются как константы времени компиляции и запекаются в месте вызова, параметры по умолчанию будут использоваться кодом только после его перекомпиляции. Создавая объект настроек, вызывающая сторона динамически загружает значения по умолчанию при вызове вашего метода. Это означает, что вы можете обновить настройки по умолчанию, просто изменив свой класс настроек. Таким образом, этот шаблон позволяет изменять значения по умолчанию без необходимости перекомпиляции вызывающих абонентов для просмотра новых значений, если это необходимо.

...