C# 4.0:我可以使用 TimeSpan 作为具有默认值的可选参数吗?
这两个都会生成一个错误,指出它们必须是编译时常量:
void Foo(TimeSpan span = TimeSpan.FromSeconds(2.0))
void Foo(TimeSpan span = new TimeSpan(2000))
首先,有人可以解释为什么这些值不能在编译时确定吗?有没有办法为可选的 TimeSpan 对象指定默认值?
Both of these generate an error saying they must be a compile-time constant:
void Foo(TimeSpan span = TimeSpan.FromSeconds(2.0))
void Foo(TimeSpan span = new TimeSpan(2000))
First of all, can someone explain why these values can't be determined at compile time? And is there a way to specify a default value for an optional TimeSpan object?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(10)
您可以通过更改签名来轻松解决此问题。
我应该详细说明 - 您的示例中的这些表达式不是编译时常量的原因是因为在编译时,编译器不能简单地执行 TimeSpan.FromSeconds(2.0) 并将结果的字节粘贴到编译后的代码中。
例如,考虑一下您是否尝试改用 DateTime.Now。 DateTime.Now 的值每次执行时都会发生变化。或者假设 TimeSpan.FromSeconds 考虑了重力。这是一个荒谬的例子,但编译时常量的规则不会仅仅因为我们碰巧知道 TimeSpan.FromSeconds 是确定性的而产生特殊情况。
You can work around this very easily by changing your signature.
I should elaborate - the reason those expressions in your example are not compile-time constants is because at compile time, the compiler can't simply execute TimeSpan.FromSeconds(2.0) and stick the bytes of the result into your compiled code.
As an example, consider if you tried to use DateTime.Now instead. The value of DateTime.Now changes every time it's executed. Or suppose that TimeSpan.FromSeconds took into account gravity. It's an absurd example but the rules of compile-time constants don't make special cases just because we happen to know that TimeSpan.FromSeconds is deterministic.
我的 VB6 传统让我对将“空值”和“缺失值”视为等价的想法感到不安。在大多数情况下,这可能没问题,但可能会产生意想不到的副作用,或者可能会陷入异常情况(例如,如果
span
的源是一个不应该为 null 的属性或变量,但是是)。因此我会重载该方法:
My VB6 heritage makes me uneasy with the idea of considering "null value" and "missing value" to be equivalent. In most cases, it's probably fine, but you might have an unintended side effect, or you might swallow an exceptional condition (for example, if the source of
span
is a property or variable that should not be null, but is).I would therefore overload the method:
这工作正常:
void Foo(TimeSpan span = default(TimeSpan))
注意:
default(TimeSpan) == TimeSpan.Zero
This works fine:
void Foo(TimeSpan span = default(TimeSpan))
Note:
default(TimeSpan) == TimeSpan.Zero
可用作默认值的值集与可用于属性参数的值集相同。原因是默认值被编码到
DefaultParameterValueAttribute
内部的元数据中。至于为什么不能在编译时确定。编译时允许的值集和此类值的表达式在官方 C# 语言规范:
TimeSpan
类型不适合任何这些列表,因此不能用作常量。The set of values which can be used as a default value are the same as can be used for an attribute argument. The reason being that default values are encoded into metadata inside of the
DefaultParameterValueAttribute
.As to why it can't be determined at compile time. The set of values and expressions over such values allowed at compile time is listed in official C# language spec:
The type
TimeSpan
does not fit into any of these lists and hence cannot be used as a constant.提供的
default(TimeSpan)
不是该函数的有效值。或者
提供的
new TimeSpan()
不是有效值。或者
考虑到
null
值作为函数有效值的机会很少,这应该更好。provided
default(TimeSpan)
is not a valid value for the function.Or
provided
new TimeSpan()
is not a valid value.Or
This should be better considering chances of
null
value being a valid value for the function are rare.TimeSpan
是DefaultValueAttribute
的特殊情况,并使用可通过TimeSpan.Parse
方法解析的任何字符串指定。TimeSpan
is a special case for theDefaultValueAttribute
and is specified using any string that can be parsed via theTimeSpan.Parse
method.我的建议:
顺便说一句
TimeSpan.FromSeconds(2.0)
不等于new TimeSpan(2000)
- 构造函数需要刻度。My suggestion:
BTW
TimeSpan.FromSeconds(2.0)
does not equalnew TimeSpan(2000)
- the constructor takes ticks.其他答案对于为什么可选参数不能是动态表达式给出了很好的解释。但是,回顾一下,默认参数的行为类似于编译时常量。这意味着编译器必须能够评估它们并给出答案。有些人希望 C# 添加对编译器在遇到常量声明时评估动态表达式的支持 - 这种功能与将方法标记为“纯”有关,但这现在还不是现实,也可能永远不会成为现实。
对于此类方法使用 C# 默认参数的一种替代方法是使用
XmlReaderSettings
。在此模式中,定义一个具有无参数构造函数和公共可写属性的类。然后用该类型的对象替换方法中所有默认选项。甚至可以通过指定默认值null
来使该对象可选。例如:要调用,请使用一种奇怪的语法在一个表达式中实例化和分配属性:
缺点
这是解决此问题的一种非常重量级的方法。如果您正在编写一个快速而肮脏的内部接口,并且使
TimeSpan
可以为空并将null视为您想要的默认值 可以正常工作,请改为这样做。此外,如果您有大量参数或在紧密循环中调用该方法,这将产生类实例化的开销。当然,如果在紧密循环中调用这样的方法,那么重用 FooSettings 对象的实例可能是自然的,甚至非常容易。
好处
正如我在示例的评论中提到的,我认为这种模式非常适合公共 API。向类添加新属性是一项不间断的 ABI 更改,因此您可以添加新的可选参数,而无需使用此模式更改方法的签名 - 为最近编译的代码提供更多选项,同时继续支持旧的编译代码,无需额外工作。
此外,由于 C# 的内置默认方法参数被视为编译时常量并烘焙到调用站点中,因此默认参数只有在重新编译后才会被代码使用。通过实例化设置对象,调用者在调用您的方法时动态加载默认值。这意味着您只需更改设置类即可更新默认值。因此,此模式允许您更改默认值,而无需重新编译调用者即可查看新值(如果需要)。
Other answers have given great explanations as to why an optional parameter cannot be a dynamic expression. But, to recount, default parameters behave like compile time constants. That means the compiler has to be able to evaluate them and come up with an answer. There are some people who want C# to add support for the compiler evaluating dynamic expressions when encountering constant declarations—this sort of feature would be related to marking methods “pure”, but that isn’t a reality right now and might never be.
One alternative to using a C# default parameter for such a method would be to use the pattern exemplified by
XmlReaderSettings
. In this pattern, define a class with a parameterless constructor and publicly writable properties. Then replace all options with defaults in your method with an object of this type. Even make this object optional by specifying a default ofnull
for it. For example:To call, use that one weird syntax for instantiating and assigning properties all in one expression:
Downsides
This is a really heavyweight approach to solving this problem. If you are writing a quick and dirty internal interface and making the
TimeSpan
nullable and treating null like your desired default value would work fine, do that instead.Also, if you have a large number of parameters or are calling the method in a tight loop, this will have the overhead of class instantiations. Of course, if calling such a method in a tight loop, it might be natural and even very easy to reuse an instance of the
FooSettings
object.Benefits
As I mentioned in the comment in the example, I think this pattern is great for public APIs. Adding new properties to a class is a non-breaking ABI change, so you can add new optional parameters without changing the signature of your method using this pattern—giving more recently compiled code more options while continuing to support old compiled code with no extra work.
Also, because C#’s built in default method parameters are treated as compiletime constants and baked into the callsite, default parameters will only be used by code once it is recompiled. By instantiating a settings object, the caller dynamically loads the default values when calling your method. This means that you can update defaults by just changing your settings class. Thus, this pattern lets you change default values without having to recompile callers to see the new values, if that is desired.
我意识到这已经很老了,很久以前就回答了,但我发现自己在读它并且......
这似乎是“假装”这是超载的最好方法。
如果是“想要”,您可以提供 2 个重载:
这允许干净地调用
Foo()
来执行您想要的操作。 int 重载通常看起来也不错。如果愿意,您可以将 int 重载更改为以秒为单位/任何单位。如果您已经有复杂的参数列表,这可能不可行,但在许多情况下,这似乎是 C# 中的“最佳”方式。
I realise this is very old and answered long ago, but I found myself reading it and...
It seems to be the best way to 'fake' this is with an overload.
Is 'wanted', you can provide 2 overloads:
This allows clean calls to
Foo()
to do what you want. The int overload seems okay in general too. You could change the int overload to be in seconds / whatever units if preferred.If you already have complex argument list this may not be viable, but in many cases this seem to be 'the' way to go in c#.
要指定结构类型参数的默认值,我建议使用重载:
To specify a default value for struct type parameters, I would suggest to use overload: