C#4.0:TimeSpanを省略可能なパラメーターとして既定値で使用できますか?


125

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

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

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


11
質問の内容とは関係ありnew TimeSpan(2000)ませんが、2000ミリ秒ではないことに注意してください。これは、0.2ミリ秒、つまり2秒の10,000分の1である2000の「ティック」を意味します。
Jeppe Stig Nielsen 2013

回答:


172

署名を変更することで、これを非常に簡単に回避できます。

void Foo(TimeSpan? span = null) {

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

   ...

}

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

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


15
<param>署名には表示されないため、ここでデフォルト値を文書化します。
大佐パニック

3
これはできません。特別な値nullを他のものに使用しています。
大佐パニック

4
@MattHickford-次に、オーバーロードされたメソッドを提供するか、パラメーターとしてミリ秒を使用する必要があります。
Josh

19
span = span ?? TimeSpan.FromSeconds(2.0);メソッド本体で、null許容型と一緒に使用することもできます。またはvar realSpan = span ?? TimeSpan.FromSeconds(2.0);、nullにできないローカル変数を取得します。
Jeppe Stig Nielsen 2013

5
これについて私が気に入らないのは、この関数がnullスパンで「機能する」ことを関数のユーザーに示唆していることです。しかし、それは真実ではありません!関数の実際のロジックに関する限り、Nullはスパンの有効な値ではありません。コードのにおいのように見えないより良い方法があったらいいのに…
JoeCool

31

私のVB6の遺産は、 "null値"と "missing value"を同等であると考えるという考えに不安を感じます。ほとんどの場合、問題はありませんが、意図しない副作用が発生したり、例外的な状態が発生したりする可能性があります(たとえば、ソースspanがnullであってはならないプロパティまたは変数である場合)。

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

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

1
素晴らしいテクニックの+1。デフォルトのパラメータは、実際にはconstタイプでのみ使用する必要があります。そうでなければ、それは信頼できません。
Lazlo、2011年

2
これは、デフォルト値が置き換えられた「時間を尊重する」アプローチであり、この状況では、これは最も醜い答えではないと思います;)ただし、デフォルト値が本当に必要なため、それだけでは必ずしもインターフェースにはうまく機能しません一箇所。この場合、拡張メソッドが便利なツールであることがわかりました。インターフェースにはすべてのパラメーターを持つ1つのメソッドがあり、静的クラスで宣言された一連の拡張メソッドは、インターフェースとともに、さまざまなオーバーロードでデフォルトを実装します。
OlduwanSteve 14

23

これはうまくいきます:

void Foo(TimeSpan span = default(TimeSpan))


4
Stack Overflowへようこそ。あなたの答えは、あなたがいることのように見えることができます限り、それはコンパイラができること1つの非常に特定の値だと、デフォルトのパラメータ値を提供します。私はそれを理解しましたか?(あなたがすることができ、編集明確にするためにあなたの答えを。)それはコンパイラが任意持つことだった、質問はもともと求めどのように取得することができます何を利用する方法を示した場合、これは良い答えになる TimeSpan、このようなことで与えられたものなどの値を、new TimeSpan(2000)
ロブ・ケネディ

2
特定のデフォルト値を使用する別の方法は、プライベートの静的な読み取り専用TimeSpan defaultTimespan = Timespan.FromSeconds(2)をデフォルトのコンストラクターと組み合わせて使用​​し、コンストラクターがタイムスパンを使用することです。public Foo():this(defaultTimespan)およびpublic Foo(
Timespan

15

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

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

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

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

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

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


2
わずかなnit-pick:静的メソッドの呼び出しは、どのリストにも適合しません。TimeSpanこのリストの最後の1つに合わせることができdefault(TimeSpan)ます。
CodesInChaos

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


4

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

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

3

私のおすすめ:

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

    //...
}

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


2

他の回答では、オプションのパラメーターを動的な式にすることができない理由について、すばらしい説明がなされています。ただし、再計算すると、デフォルトのパラメーターはコンパイル時定数のように動作します。つまり、コンパイラーはそれらを評価して答えを出す必要があります。定数宣言に遭遇したときに動的な式を評価するコンパイラーのサポートを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つの奇妙な構文を使用して、すべてを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

欠点

これは、この問題を解決するための本当に重いアプローチです。すばやくダーティな内部インターフェイスを作成し、TimeSpannullを可能にして、nullを目的のデフォルト値のように扱うことがうまく機能する場合は、代わりにそれを行います。

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

利点

例のコメントで述べたように、このパターンはパブリックAPIに最適だと思います。クラスに新しいプロパティを追加することはABIの変更ではないので、このパターンを使用してメソッドのシグネチャを変更せずに新しいオプションのパラメーターを追加できます。追加の作業なしで古いコンパイル済みコードをサポートしながら、最近コンパイルされたコードにより多くのオプションを提供できます。 。

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.