这两个都会产生错误,说它们必须是编译时常量:
void Foo(TimeSpan span = TimeSpan.FromSeconds(2.0))
void Foo(TimeSpan span = new TimeSpan(2000))
首先,有人可以解释为什么在编译时无法确定这些值吗?有没有办法为可选的TimeSpan对象指定默认值?
答案 0 :(得分:155)
您可以通过更改签名轻松解决此问题。
void Foo(TimeSpan? span = null) {
if (span == null) { span = TimeSpan.FromSeconds(2); }
...
}
我应该详细说明 - 你的例子中的那些表达式不是编译时常量的原因是因为在编译时,编译器不能简单地执行TimeSpan.FromSeconds(2.0)并将结果的字节粘贴到已编译的代码中
作为示例,请考虑您是否尝试使用DateTime.Now。 DateTime.Now的值每次执行时都会更改。或者假设TimeSpan.FromSeconds考虑了重力。这是一个荒谬的例子,但编译时常量的规则并不会因为我们碰巧知道TimeSpan.FromSeconds是确定性的而出现特殊情况。
答案 1 :(得分:25)
我的VB6遗产让我感到不安,认为“空值”和“缺失值”是等价的。在大多数情况下,它可能没什么问题,但是你可能会产生意想不到的副作用,或者你可能会吞下一个异常条件(例如,如果span
的来源是一个不应为null的属性或变量,那么)。
因此我会重载该方法:
void Foo()
{
Foo(TimeSpan.FromSeconds(2.0));
}
void Foo(TimeSpan span)
{
//...
}
答案 2 :(得分:18)
这很好用:
void Foo(TimeSpan span = default(TimeSpan))
答案 3 :(得分:13)
可用作默认值的值集与可用于属性参数的值相同。原因是默认值被编码到DefaultParameterValueAttribute
内的元数据中。
至于为何在编译时无法确定。在编译时允许的值和表达式的集合列在官方C# language spec:
中C# 6.0 - Attribute parameter types
属性类的位置和命名参数类型仅限于属性参数类型,它们是:
- 以下类型之一:
bool
,byte
,char
,double
,float
,int
,long
,sbyte
,short
,string
,uint
,ulong
,ushort
。- 类型
object
。- 类型
System.Type
。- 枚举类型。
(前提是它具有公共可访问性,并且嵌套的类型(如果有的话)也具有公共可访问性)- 上述类型的一维数组。
类型TimeSpan
不适合任何这些列表,因此不能用作常量。
答案 4 :(得分:11)
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
值为函数的有效值的可能性很小,这应该会更好。
答案 5 :(得分:4)
TimeSpan
是DefaultValueAttribute
的一个特例,使用任何可以通过TimeSpan.Parse
方法解析的字符串来指定。
[DefaultValue("0:10:0")]
public TimeSpan Duration { get; set; }
答案 6 :(得分:2)
Other answers已经给出了很好的解释,为什么可选参数不能是动态表达式。但是,要重新计算,默认参数的行为类似于编译时常量。这意味着编译器必须能够对它们进行评估并得出答案。有些人希望C#在遇到常量声明时添加对编译器评估动态表达式的支持 - 这种特性与标记方法“纯粹”有关,但这现在不是现实,可能永远不会成为现实。 / p>
对这种方法使用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
这是解决此问题的一种非常重要的方法。如果你正在编写一个快速而肮脏的内部界面,并且making the TimeSpan
nullable and treating null like your desired default value可以正常工作,那就改为。
此外,如果您有大量参数或正在紧密循环中调用该方法,则会产生类实例化的开销。当然,如果在紧密循环中调用这样的方法,重用FooSettings
对象的实例可能很自然,甚至非常容易。
正如我在示例中的评论中提到的,我认为这种模式非常适合公共API。向类添加新属性是一个非破坏性的ABI更改,因此您可以使用此模式添加新的可选参数而无需更改方法的签名 - 为最近编译的代码提供更多选项,同时继续支持旧编译代码而无需额外工作
此外,由于C#内置的默认方法参数被视为编译时常量并被烘焙到调用点,因此默认参数只有在重新编译后才会被代码使用。通过实例化设置对象,调用方在调用方法时动态加载默认值。这意味着您只需更改设置类即可更新默认值。因此,此模式允许您更改默认值,而无需重新编译调用者以查看新值(如果需要)。
答案 7 :(得分:2)
我的建议:
void A( long spanInMs = 2000 )
{
var ts = TimeSpan.FromMilliseconds(spanInMs);
//...
}
顺便说一句
TimeSpan.FromSeconds(2.0)
不等于new TimeSpan(2000)
- 构造函数需要刻度。