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


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

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

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

8 96

8 ответов:

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

void Foo(TimeSpan? span = null) {

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

   ...

}

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

в качестве примера рассмотрим, пытались ли вы использовать DateTime.Теперь вместо этого. Значение DateTime.Сейчас меняется каждый раз, когда он выполняется. Или предположим, что промежуток времени.Из секунд учитывалась гравитация. Это абсурдный пример, но правила констант времени компиляции не делают особых случаев только потому, что мы знаем этот промежуток времени.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# добавлял поддержку компилятора, оценивающего динамические выражения при столкновении с объявлениями констант-такая функция была бы связана с методами маркировки "чисто", но это не реальность прямо сейчас и может никогда не быть.

одной из альтернатив использования параметра 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);

    //...
}

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