Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/csharp-4.0/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C#4.0:我可以使用TimeSpan作为带有默认值的可选参数吗?_C#_C# 4.0_Default Value_Timespan_Optional Parameters - Fatal编程技术网

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);

    //...
}