C# 4.0:我可以使用 TimeSpan 作为具有默认值的可选参数吗?

发布于 2024-08-20 05:01:00 字数 216 浏览 4 评论 0原文

这两个都会生成一个错误,指出它们必须是编译时常量:

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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

梦回梦里 2024-08-27 05:01:00

您可以通过更改签名来轻松解决此问题。

void Foo(TimeSpan? span = null) {

   if (span == null) { span = TimeSpan.FromSeconds(2); }

   ...

}

我应该详细说明 - 您的示例中的这些表达式不是编译时常量的原因是因为在编译时,编译器不能简单地执行 TimeSpan.FromSeconds(2.0) 并将结果的字节粘贴到编译后的代码中。

例如,考虑一下您是否尝试改用 DateTime.Now。 DateTime.Now 的值每次执行时都会发生变化。或者假设 TimeSpan.FromSeconds 考虑了重力。这是一个荒谬的例子,但编译时常量的规则不会仅仅因为我们碰巧知道 TimeSpan.FromSeconds 是确定性的而产生特殊情况。

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.

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.

歌入人心 2024-08-27 05:01:00

我的 VB6 传统让我对将“空值”和“缺失值”视为等价的想法感到不安。在大多数情况下,这可能没问题,但可能会产生意想不到的副作用,或者可能会陷入异常情况(例如,如果 span 的源是一个不应该为 null 的属性或变量,但是是)。

因此我会重载该方法:

void Foo()
{
    Foo(TimeSpan.FromSeconds(2.0));
}
void Foo(TimeSpan span)
{
    //...
}

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()
{
    Foo(TimeSpan.FromSeconds(2.0));
}
void Foo(TimeSpan span)
{
    //...
}
十年九夏 2024-08-27 05:01:00

这工作正常:

void Foo(TimeSpan span = default(TimeSpan))

注意:default(TimeSpan) == TimeSpan.Zero

This works fine:

void Foo(TimeSpan span = default(TimeSpan))

Note: default(TimeSpan) == TimeSpan.Zero

傲影 2024-08-27 05:01:00

可用作默认值的值集与可用于属性参数的值集相同。原因是默认值被编码到 DefaultParameterValueAttribute 内部的元数据中。

至于为什么不能在编译时确定。编译时允许的值集和此类值的表达式在官方 C# 语言规范

C# 6.0 - 属性参数类型

属性类的位置参数和命名参数的类型仅限于属性参数类型,它们是:

  • 以下类型之一:boolbytechardoublefloat< /code>、intlongsbyteshort字符串、<代码>uintulongushort
  • 类型对象
  • 类型System.Type
  • 枚举类型。
    (前提是它具有公共可访问性,并且它嵌套的类型(如果有)也具有公共可访问性)
  • 上述类型的一维数组。

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:

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.

The type TimeSpan does not fit into any of these lists and hence cannot be used as a constant.

素衣风尘叹 2024-08-27 05:01:00
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 值作为函数有效值的机会很少,这应该更好。

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.

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.

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.

北城孤痞 2024-08-27 05:01:00

TimeSpanDefaultValueAttribute 的特殊情况,并使用可通过 TimeSpan.Parse 方法解析的任何字符串指定。

[DefaultValue("0:10:0")]
public TimeSpan Duration { get; set; }

TimeSpan is a special case for the DefaultValueAttribute and is specified using any string that can be parsed via the TimeSpan.Parse method.

[DefaultValue("0:10:0")]
public TimeSpan Duration { get; set; }
情泪▽动烟 2024-08-27 05:01:00

我的建议:

void A( long spanInMs = 2000 )
{
    var ts = TimeSpan.FromMilliseconds(spanInMs);

    //...
}

顺便说一句
TimeSpan.FromSeconds(2.0) 不等于 new TimeSpan(2000) - 构造函数需要刻度。

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.

回梦 2024-08-27 05:01:00

其他答案对于为什么可选参数不能是动态表达式给出了很好的解释。但是,回顾一下,默认参数的行为类似于编译时常量。这意味着编译器必须能够评估它们并给出答案。有些人希望 C# 添加对编译器在遇到常量声明时评估动态表达式的支持 - 这种功能与将方法标记为“纯”有关,但这现在还不是现实,也可能永远不会成为现实。

对于此类方法使用 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

缺点

这是解决此问题的一种非常重量级的方法。如果您正在编写一个快速而肮脏的内部接口,并且使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 of null for it. For example:

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 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.

小苏打饼 2024-08-27 05:01:00

我意识到这已经很老了,很久以前就回答了,但我发现自己在读它并且......

这似乎是“假装”这是超载的最好方法。

void Foo(TimeSpan span = new TimeSpan(2000))

如果是“想要”,您可以提供 2 个重载:

void Foo(int ticks = 2000) => Foo(new TimeSpan(ticks));
void Foo(TimeSpan span) => [implementation]

这允许干净地调用 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.

void Foo(TimeSpan span = new TimeSpan(2000))

Is 'wanted', you can provide 2 overloads:

void Foo(int ticks = 2000) => Foo(new TimeSpan(ticks));
void Foo(TimeSpan span) => [implementation]

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#.

小ぇ时光︴ 2024-08-27 05:01:00

要指定结构类型参数的默认值,我建议使用重载:

void Foo() 
{
    Foo(TimeSpan.FromSeconds(2.0));
}

void Foo(TimeSpan time)
{
    // ...
}

To specify a default value for struct type parameters, I would suggest to use overload:

void Foo() 
{
    Foo(TimeSpan.FromSeconds(2.0));
}

void Foo(TimeSpan time)
{
    // ...
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文