C # 4.0: Можно ли использовать TimeSpan в качестве необязательного параметра со значением по умолчанию?
Оба они генерируют ошибку, говоря, что они должны быть константой времени компиляции:
void Foo(TimeSpan span = TimeSpan.FromSeconds(2.0)) void Foo(TimeSpan span = new TimeSpan(2000))
Прежде всего, может ли кто-нибудь объяснить, почему эти значения не могут быть определены во время компиляции? И есть ли способ указать значение по умолчанию для необязательного объекта TimeSpan?
- Дополнительные параметры Java
- Передача пустого массива в качестве значения по умолчанию для необязательного параметра
- Как использовать необязательные параметры в C #?
- C # 4.0 необязательные аргументы out / ref
- Дополнительные параметры C # для переопределенных методов
- Могу ли я иметь необязательный параметр для веб-службы ASP.NET SOAP
- избегая скуки факультативных параметров
- Как передать тип с нулевым значением в функцию P / invoked
- C # 4.0, необязательные параметры и параметры не работают вместе
- Действие параметра , в котором T3 может быть необязательным
- Как использовать необязательные параметры в хранимой процедуре T-SQL?
- Как обрабатывать нулевые или необязательные параметры структуры dll в C #
Вы можете легко обойти это, изменив свою подпись.
void Foo(TimeSpan? span = null) { if (span == null) { span = TimeSpan.FromSeconds(2); } ... }
Я должен уточнить – причина, по которой эти выражения в вашем примере не являются константами времени компиляции, заключается в том, что во время компиляции компилятор не может просто выполнить TimeSpan.FromSeconds (2.0) и привязать байты результата к скомпилированному коду.
В качестве примера рассмотрим, пытались ли вы использовать DateTime.Now. Значение DateTime.Now изменяется каждый раз, когда оно выполняется. Или предположим, что TimeSpan.FromSeconds учитывает гравитацию. Это абсурдный пример, но правила констант времени компиляции не делают особых случаев только потому, что мы знаем, что TimeSpan.FromSeconds детерминирован.
Мое наследие VB6 заставляет меня беспокоиться о том, чтобы идея «нулевое значение» и «недостающее значение» были эквивалентными. В большинстве случаев это, вероятно, хорошо, но у вас может быть непреднамеренный побочный эффект, или вы можете проглотить исключительное условие (например, если источником span
является свойство или переменная, которая не должна быть нулевой, но есть).
Поэтому я бы перегрузил метод:
void Foo() { Foo(TimeSpan.FromSeconds(2.0)); } void Foo(TimeSpan span) { //... }
Это прекрасно работает:
void Foo(TimeSpan span = default(TimeSpan))
Набор значений, которые могут использоваться как значение по умолчанию, такие же, как и для аргумента атрибута. Причина в том, что значения по умолчанию закодированы в метаданные внутри DefaultParameterValueAttribute
.
Почему это невозможно определить во время компиляции. Набор значений и выражений над такими значениями, разрешенными во время компиляции, указан в официальной спецификации языка C # :
C # 6.0 – Типы параметров атрибута :
Типы позиционных и именованных параметров для classа атрибутов ограничены типами параметров атрибутов , которые:
- Один из следующих типов:
bool
,byte
,char
,double
,float
,int
,long
,sbyte
,short
,string
,uint
,ulong
,ushort
.- Тип
object
.- Тип
System.Type
.- Тип enums.
(при условии, что он имеет общедоступную доступность, а типы, в которых он вложен (если есть), также имеют общедоступную доступность)- Одномерные массивы вышеуказанных типов.
Тип 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
. В этом шаблоне определите class с безпараллельным конструктором и общедоступными свойствами. Затем замените все параметры по умолчанию в своем методе объектом этого типа. Даже сделайте этот объект необязательным, указав для него значение по умолчанию 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). Lazy thing = new Lazy (() => new FatThing()); public IThing Thing { get { return thing.Value; } set { thing = new Lazy (() => 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
присвоить TimeSpan
null и обрабатывать нуль, как ваше желаемое значение по умолчанию, оно будет работать нормально, сделайте это вместо этого.
Кроме того, если у вас есть большое количество параметров или вы вызываете метод в узком цикле, это будет иметь накладные расходы на экземпляры classов. Конечно, если вы вызываете такой метод в узком цикле, может быть естественным и даже очень простым повторное использование экземпляра объекта FooSettings
.
Выгоды
Как я упоминал в комментарии в примере, я думаю, что эта модель отлично подходит для публичных API. Добавление новых свойств в class является изменением ABI без изменений, поэтому вы можете добавлять новые необязательные параметры без изменения подписи вашего метода с помощью этого шаблона, предоставляя более недавно скомпилированный код дополнительных параметров, продолжая поддерживать старый скомпилированный код без дополнительной работы ,
Кроме того, поскольку встроенные по умолчанию параметры метода C # обрабатываются как константы компиляции и выпекаются в callsite, параметры по умолчанию будут использоваться только кодом после его перекомпиляции. Создавая экземпляр объекта настроек, вызывающий динамически загружает значения по умолчанию при вызове вашего метода. Это означает, что вы можете обновлять значения по умолчанию, просто изменив class настроек. Таким образом, этот шаблон позволяет изменять значения по умолчанию без необходимости перекомпилировать вызывающих абонентов, чтобы увидеть новые значения, если это необходимо.
Мое предложение:
void A( long spanInMs = 2000 ) { var ts = TimeSpan.FromMilliseconds(spanInMs); //... }
BTW TimeSpan.FromSeconds(2.0)
не соответствует new TimeSpan(2000)
– конструктор принимает тики.