web-dev-qa-db-ja.com

C#4.0:TimeSpanをデフォルト値を持つオプションのパラメーターとして使用できますか?

これらは両方とも、コンパイル時の定数でなければならないというエラーを生成します。

void Foo(TimeSpan span = TimeSpan.FromSeconds(2.0))
void Foo(TimeSpan span = new TimeSpan(2000))

まず、これらの値がコンパイル時に決定できない理由を誰かが説明できますか?また、オプションのTimeSpanオブジェクトのデフォルト値を指定する方法はありますか?

113
Mike Pateras

署名を変更することで、この問題を簡単に回避できます。

void Foo(TimeSpan? span = null) {

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

   ...

}

詳しく説明する必要があります-例のこれらの式がコンパイル時の定数ではない理由は、コンパイル時にコンパイラが単にTimeSpan.FromSeconds(2.0)を実行して結果のバイトをコンパイル済みコードに固定できないためです。

例として、代わりにDateTime.Nowを使用しようとしたかどうかを検討してください。 DateTime.Nowの値は、実行されるたびに変化します。または、TimeSpan.FromSecondsが重力を考慮したと仮定します。これはばかげた例ですが、コンパイル時定数の規則は、TimeSpan.FromSecondsが決定論的であることを知っているからといって、特別なケースを作成しません。

161
Josh

私のVB6の遺産は、「null値」と「missing value」を同等と考えるという考えに不安を抱かせます。ほとんどの場合、おそらく問題ありませんが、意図しない副作用が発生したり、例外条件を飲み込んだりする可能性があります(たとえば、spanのソースがnullではないプロパティまたは変数である場合、です)。

したがって、メソッドをオーバーロードします。

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

これはうまくいきます:

void Foo(TimeSpan span = default(TimeSpan))

19

デフォルト値として使用できる値のセットは、属性引数に使用できるものと同じです。理由は、デフォルト値がDefaultParameterValueAttribute内のメタデータにエンコードされるためです。

コンパイル時に判断できない理由について。コンパイル時に許可される値とそのような値の式のセットは、公式の C#言語仕様 にリストされています。

C#6.0-属性パラメータータイプ

属性クラスの位置パラメータおよび名前付きパラメータのタイプは、属性パラメータタイプに制限されています。

  • 次のタイプのいずれか:boolbytechardoublefloatintlongsbyteshortstringuintulongushort
  • タイプobject
  • タイプ System.Type
  • 列挙型。
    (ただし、パブリックアクセシビリティがあり、ネストされているタイプ(存在する場合)もパブリックアクセシビリティがある場合)
  • 上記のタイプの1次元配列。

タイプTimeSpanはこれらのリストのいずれにも適合しないため、定数として使用できません。

13
JaredPar
_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値が関数の有効な値である可能性はまれであることを考慮すると、これはより良いはずです。

11
nawfal

TimeSpanDefaultValueAttributeの特殊なケースであり、TimeSpan.Parseメソッドを介して解析できる任意の文字列を使用して指定されます。

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

私のおすすめ:

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

    //...
}
_

ところでTimeSpan.FromSeconds(2.0)new TimeSpan(2000)と等しくありません-コンストラクターはティックを取得します。

3
tymtam

その他の回答 は、オプションのパラメーターが動的な式になれない理由について素晴らしい説明を与えました。ただし、詳しく説明すると、デフォルトのパラメーターはコンパイル時定数のように動作します。つまり、コンパイラーはそれらを評価して答えを出さなければなりません。定数宣言に遭遇したときに動的式を評価するコンパイラのサポートを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);
    }
}

呼び出すには、1つの式でプロパティをインスタンス化して割り当てるための奇妙な構文を使用します。

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可能にし、nullを目的のデフォルト値のように処理する が正常に機能する場合は、代わりにそれを実行します。

また、多数のパラメーターがある場合、またはタイトなループでメソッドを呼び出している場合、これにはクラスのインスタンス化のオーバーヘッドがあります。もちろん、このようなメソッドをタイトループで呼び出す場合、FooSettingsオブジェクトのインスタンスを再利用するのは自然であり、非常に簡単です。

利点

例のコメントで述べたように、このパターンはパブリックAPIに最適だと思います。クラスに新しいプロパティを追加することはABIの変更ではないため、このパターンを使用してメソッドのシグネチャを変更せずに新しいオプションのパラメーターを追加できます。 。

また、C#に組み込まれているデフォルトのメソッドパラメータはコンパイル時の定数として扱われ、コールサイトにベイク処理されるため、デフォルトパラメータは、再コンパイルされたコードでのみ使用されます。設定オブジェクトをインスタンス化することにより、呼び出し元はメソッドを呼び出すときにデフォルト値を動的にロードします。これは、設定クラスを変更するだけでデフォルトを更新できることを意味します。したがって、このパターンを使用すると、必要に応じて呼び出し側を再コンパイルして新しい値を確認することなく、デフォルト値を変更できます。

2
binki