C# 4.0: можно ли использовать TimeSpan в качестве необязательного параметра со значением по умолчанию?

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

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

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

8 ответов


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

void Foo(TimeSpan? span = null) {

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

   ...

}

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

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


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

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

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

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

void Foo(TimeSpan span = default(TimeSpan))


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

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

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

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

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

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


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 значение, являющееся допустимым значением для функции, встречается редко.


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

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

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

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

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


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

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

    //...
}

BTW TimeSpan.FromSeconds(2.0) Не равно new TimeSpan(2000) - конструктор принимает клещей.