C#4.0:我可以使用TimeSpan作为带有默认值的可选参数吗?
这两个参数都会生成一个错误,表示它们必须是编译时常量:C#4.0:我可以使用TimeSpan作为带有默认值的可选参数吗?,c#,c#-4.0,default-value,timespan,optional-parameters,C#,C# 4.0,Default Value,Timespan,Optional Parameters,这两个参数都会生成一个错误,表示它们必须是编译时常量: void Foo(TimeSpan span = TimeSpan.FromSeconds(2.0)) void Foo(TimeSpan span = new TimeSpan(2000)) 首先,有人能解释为什么不能在编译时确定这些值吗?有没有办法为可选的TimeSpan对象指定默认值?可以用作默认值的值集与可以用于属性参数的值集相同。原因是默认值被编码到DefaultParameterValueAttribute中的元数据中 至于为
void Foo(TimeSpan span = TimeSpan.FromSeconds(2.0))
void Foo(TimeSpan span = new TimeSpan(2000))
首先,有人能解释为什么不能在编译时确定这些值吗?有没有办法为可选的TimeSpan对象指定默认值?可以用作默认值的值集与可以用于属性参数的值集相同。原因是默认值被编码到
DefaultParameterValueAttribute
中的元数据中
至于为什么不能在编译时确定。官方文件中列出了编译时允许的值和表达式集:
:
属性类的位置参数和命名参数的类型仅限于属性参数类型,即:
- 以下类型之一:
,bool
,byte
,char
,double
,float
,int
,long
,sbyte
,string
,uint
,ulong
ushort
- 类型
对象
- 类型
System.type
- 枚举类型。
(前提是它具有公共可访问性,并且嵌套它的类型(如果有)也具有公共可访问性) - 上述类型的一维数组
类型
TimeSpan
不适合这些列表中的任何一个,因此不能用作常量 通过更改签名,您可以非常轻松地解决此问题
void Foo(TimeSpan? span = null) {
if (span == null) { span = TimeSpan.FromSeconds(2); }
...
}
我应该详细说明一下-示例中的这些表达式不是编译时常量的原因是,在编译时,编译器不能简单地执行TimeSpan.FromSeconds(2.0)并将结果的字节粘贴到编译代码中
作为一个例子,考虑是否尝试使用DATETIME。DateTime.Now的值在每次执行时都会更改。或者假设TimeSpan.FromSeconds考虑了重力。这是一个荒谬的例子,但编译时常量的规则并没有特例,因为我们碰巧知道TimeSpan.FromSeconds是确定性的。
我的VB6遗产让我不太愿意将“空值”和“缺失值”等同起来。在大多数情况下,这可能没什么问题,但可能会产生意外的副作用,或者可能会出现异常情况(例如,如果span
的源是一个属性或变量,该属性或变量不应为null,而应为null)
因此,我将重载该方法:
void Foo()
{
Foo(TimeSpan.FromSeconds(2.0));
}
void Foo(TimeSpan span)
{
//...
}
TimeSpan
是DefaultValueAttribute
的一个特例,它使用可以通过TimeSpan.Parse
方法解析的任何字符串指定
[DefaultValue("0:10:0")]
public TimeSpan Duration { get; set; }
这很好:
void Foo(TimeSpan=default(TimeSpan))
注意:default(TimeSpan)=TimeSpan.Zero
void Foo(TimeSpan span = default(TimeSpan))
{
if (span == default(TimeSpan))
span = TimeSpan.FromSeconds(2);
}
提供的default(TimeSpan)
不是该函数的有效值
或
提供的new TimeSpan()
不是有效值
或
考虑到null
值成为函数有效值的可能性很小,这应该更好。对可选参数不能是动态表达式的原因给出了很好的解释。但是,要重新说明,默认参数的行为类似于编译时常量。这意味着编译器必须能够对它们进行评估并给出答案。有些人希望C#在遇到常量声明时增加对编译器计算动态表达式的支持。这种特性可能与将方法标记为“纯”有关,但目前这不是现实,也可能永远不会
对于这种方法,使用C#默认参数的一种替代方法是使用以下示例中的模式。在此模式中,定义一个具有无参数构造函数和公共可写属性的类。然后用此类型的对象替换方法中的所有默认选项。甚至可以通过为该对象指定默认值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);
}
}
缺点
这是解决这个问题的一个非常重要的方法。如果您正在编写一个快速而肮脏的内部接口,并且可以很好地工作,那么请改为这样做
此外,如果您有大量的参数,或者正在紧密循环中调用该方法,这将增加类实例化的开销。当然,如果在一个紧密循环中调用这样一个方法,那么重用FooSettings
对象的实例可能很自然,甚至很容易
利益
正如我在示例的注释中提到的,我认为这种模式非常适合公共API。向类添加新属性是一个不间断的ABI更改,因此您可以添加新的可选参数,而无需使用此模式更改方法的签名,从而为最近编译的代码提供更多选项,同时继续支持旧的编译代码,而无需额外工作
此外,由于C#的内置默认方法参数被视为compiletime常量并烘焙到callsite中,因此默认参数只有在重新编译后才会被代码使用。通过实例化设置对象,调用方在调用方法时动态加载默认值。这意味着您可以通过更改设置类来更新默认值。因此,此模式允许您更改默认值,而无需重新编译调用者以查看新值(如果需要)。我的建议:
void A( long spanInMs = 2000 )
{
var ts = TimeSpan.FromMilliseconds(spanInMs);
//...
}
顺便说一句
TimeSpan.FromSeconds(2.0)
不等于newtimespan(2000)
——构造函数会记下记号。+1表示这项伟大的技术。默认参数应该只用于常量类型,真的。否则,这是不可靠的。这是一种“久经考验”的方法,默认值被取代,在这种情况下,我认为这是不可靠的
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
void A( long spanInMs = 2000 )
{
var ts = TimeSpan.FromMilliseconds(spanInMs);
//...
}