C# 4.0:我可以使用 TimeSpan 作为具有默认值的可选参数吗?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2168798/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
C# 4.0: Can I use a TimeSpan as an optional parameter with a default value?
提问by Mike Pateras
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?
首先,有人可以解释为什么不能在编译时确定这些值吗?有没有办法为可选的 TimeSpan 对象指定默认值?
采纳答案by Josh
You can work around this very easily by changing your signature.
您可以通过更改签名来轻松解决此问题。
void Foo(TimeSpan? span = null) {
if (span == null) { span = TimeSpan.FromSeconds(2); }
...
}
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.
我应该详细说明 - 您示例中的那些表达式不是编译时常量的原因是因为在编译时,编译器不能简单地执行 TimeSpan.FromSeconds(2.0) 并将结果的字节粘贴到您的编译代码中。
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.
例如,请考虑您是否尝试使用 DateTime.Now。DateTime.Now 的值在每次执行时都会改变。或者假设 TimeSpan.FromSeconds 考虑了重力。这是一个荒谬的例子,但编译时常量的规则并不会仅仅因为我们碰巧知道 TimeSpan.FromSeconds 是确定性的而产生特殊情况。
回答by JaredPar
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
.
可用作默认值的值集与可用于属性参数的值集相同。原因是默认值被编码到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:
至于为什么不能在编译时确定。官方C# 语言规范中列出了编译时允许的一组值和对这些值的表达式:
C# 6.0 - Attribute parameter types:
The types of positional and named parameters for an attribute class are limited to the attribute parameter types, which are:
- One of the following types:
bool
,byte
,char
,double
,float
,int
,long
,sbyte
,short
,string
,uint
,ulong
,ushort
.- The type
object
.- The type
System.Type
.- An enum type.
(provided it has public accessibility and the types in which it is nested (if any) also have public accessibility)- Single-dimensional arrays of the above types.
C# 6.0 - 属性参数类型:
属性类的位置和命名参数的类型仅限于属性参数类型,它们是:
- 以下类型之一:
bool
、byte
、char
、double
、float
、int
、long
、sbyte
、short
、string
、uint
、ulong
、ushort
。- 类型
object
。- 类型
System.Type
。- 枚举类型。
(前提是它具有公共可访问性,并且嵌套的类型(如果有)也具有公共可访问性)- 上述类型的一维数组。
The type TimeSpan
does not fit into any of these lists and hence cannot be used as a constant.
该类型TimeSpan
不适合这些列表中的任何一个,因此不能用作常量。
回答by phoog
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).
我的 VB6 传统让我对将“空值”和“缺失值”视为等效的想法感到不安。在大多数情况下,这可能没问题,但您可能会产生意想不到的副作用,或者您可能会吞下异常情况(例如,如果 ofspan
的属性或变量不应该为空,而是为空)。
I would therefore overload the method:
因此,我会重载该方法:
void Foo()
{
Foo(TimeSpan.FromSeconds(2.0));
}
void Foo(TimeSpan span)
{
//...
}
回答by dahall
TimeSpan
is a special case for the DefaultValueAttribute
and is specified using any string that can be parsed via the TimeSpan.Parse
method.
TimeSpan
是 的特例,DefaultValueAttribute
并且使用可以通过该TimeSpan.Parse
方法解析的任何字符串指定。
[DefaultValue("0:10:0")]
public TimeSpan Duration { get; set; }
回答by Elena Lavrinenko
This works fine:
这工作正常:
void Foo(TimeSpan span = default(TimeSpan))
void Foo(TimeSpan span = default(TimeSpan))
回答by nawfal
void Foo(TimeSpan span = default(TimeSpan))
{
if (span == default(TimeSpan))
span = TimeSpan.FromSeconds(2);
}
provided default(TimeSpan)
is not a valid value for the function.
提供default(TimeSpan)
的不是函数的有效值。
Or
或者
//this works only for value types which TimeSpan is
void Foo(TimeSpan span = new TimeSpan())
{
if (span == new TimeSpan())
span = TimeSpan.FromSeconds(2);
}
provided new TimeSpan()
is not a valid value.
提供的new TimeSpan()
值无效。
Or
或者
void Foo(TimeSpan? span = null)
{
if (span == null)
span = TimeSpan.FromSeconds(2);
}
This should be better considering chances of null
value being a valid value for the function are rare.
考虑到null
value 是函数的有效值的机会很少,这应该会更好。
回答by binki
Other answershave 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.
其他答案已经很好地解释了为什么可选参数不能是动态表达式。但是,重述一遍,默认参数的行为类似于编译时常量。这意味着编译器必须能够评估它们并提出答案。有些人希望 C# 在遇到常量声明时添加对编译器评估动态表达式的支持——这种特性将与标记方法“纯”相关,但这现在不是现实,也可能永远不会成为现实。
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 of null
for it. For example:
对这种方法使用 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);
}
}
To call, use that one weird syntax for instantiating and assigning properties all in one expression:
要调用,请使用一种奇怪的语法在一个表达式中实例化和分配属性:
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
缺点
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 valuewould work fine, do that instead.
这是解决这个问题的非常重量级的方法。如果您正在编写一个快速而肮脏的内部接口,并使TimeSpan
null 可以为空并将 null 视为您想要的默认值可以正常工作,请改为这样做。
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.
此外,如果您有大量参数或在紧密循环中调用该方法,则会产生类实例化的开销。当然,如果在紧密循环中调用这样的方法,重用FooSettings
对象的实例可能很自然,甚至很容易。
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.
正如我在示例中的评论中提到的,我认为这种模式非常适合公共 API。向类添加新属性是一个非破坏性的 ABI 更改,因此您可以使用此模式添加新的可选参数而无需更改方法的签名 - 为最近编译的代码提供更多选项,同时继续支持旧的编译代码而无需额外工作.
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.
此外,由于 C# 的内置默认方法参数被视为编译时常量并烘焙到调用站点中,因此默认参数只会在重新编译后由代码使用。通过实例化设置对象,调用者在调用您的方法时动态加载默认值。这意味着您可以通过更改设置类来更新默认值。因此,如果需要,此模式使您无需重新编译调用方即可更改默认值以查看新值。
回答by tymtam
My suggestion:
我的建议:
void A( long spanInMs = 2000 )
{
var ts = TimeSpan.FromMilliseconds(spanInMs);
//...
}
BTW
TimeSpan.FromSeconds(2.0)
does not equal new TimeSpan(2000)
- the constructor takes ticks.
顺便说一句
TimeSpan.FromSeconds(2.0)
,不等于new TimeSpan(2000)
- 构造函数需要滴答声。