回答:
署名を変更することで、これを非常に簡単に回避できます。
void Foo(TimeSpan? span = null) {
if (span == null) { span = TimeSpan.FromSeconds(2); }
...
}
詳しく説明する必要があります-例にあるこれらの式がコンパイル時定数ではない理由は、コンパイル時に、コンパイラーがTimeSpan.FromSeconds(2.0)を単純に実行できず、結果のバイトをコンパイル済みコードに貼り付けることができないためです。
例として、代わりにDateTime.Nowを使用しようとしたかどうかを検討してください。DateTime.Nowの値は、実行されるたびに変化します。または、TimeSpan.FromSecondsが重力を考慮したと仮定します。それはばかげた例ですが、たまたまTimeSpan.FromSecondsが確定的であることを知っているからといって、コンパイル時の定数の規則は特別なケースを作りません。
<param>
署名には表示されないため、ここでデフォルト値を文書化します。
span = span ?? TimeSpan.FromSeconds(2.0);
メソッド本体で、null許容型と一緒に使用することもできます。またはvar realSpan = span ?? TimeSpan.FromSeconds(2.0);
、nullにできないローカル変数を取得します。
私のVB6の遺産は、 "null値"と "missing value"を同等であると考えるという考えに不安を感じます。ほとんどの場合、問題はありませんが、意図しない副作用が発生したり、例外的な状態が発生したりする可能性があります(たとえば、ソースspan
がnullであってはならないプロパティまたは変数である場合)。
したがって、メソッドをオーバーロードします。
void Foo()
{
Foo(TimeSpan.FromSeconds(2.0));
}
void Foo(TimeSpan span)
{
//...
}
これはうまくいきます:
void Foo(TimeSpan span = default(TimeSpan))
デフォルト値として使用できる値のセットは、属性引数に使用できる値と同じです。その理由は、デフォルト値が内のメタデータにエンコードされているためですDefaultParameterValueAttribute
。
コンパイル時に決定できない理由について。値セットと、コンパイル時に許可されるそのような値に対する式は、公式のC#言語仕様にリストされています。
C#6.0-属性パラメーターのタイプ:
属性クラスの位置パラメータと名前付きパラメータのタイプは、次の属性パラメータタイプに制限されています。
- 次のいずれかのタイプ:
bool
、byte
、char
、double
、float
、int
、long
、sbyte
、short
、string
、uint
、ulong
、ushort
。- タイプ
object
。- タイプ
System.Type
。- 列挙型。
(ただし、パブリックアクセシビリティがあり、ネストされているタイプ(存在する場合)にもパブリックアクセシビリティがある場合)- 上記のタイプの1次元配列。
タイプTimeSpan
はこれらのリストのいずれにも適合しないため、定数として使用できません。
TimeSpan
このリストの最後の1つに合わせることができdefault(TimeSpan)
ます。
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
関数の有効な値である可能性はまれであることを考慮すると、これはより良いはずです。
他の回答では、オプションのパラメーターを動的な式にすることができない理由について、すばらしい説明がなされています。ただし、再計算すると、デフォルトのパラメーターはコンパイル時定数のように動作します。つまり、コンパイラーはそれらを評価して答えを出す必要があります。定数宣言に遭遇したときに動的な式を評価するコンパイラーのサポートを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
これは、この問題を解決するための本当に重いアプローチです。すばやくダーティな内部インターフェイスを作成し、TimeSpan
nullを可能にして、nullを目的のデフォルト値のように扱うことがうまく機能する場合は、代わりにそれを行います。
また、パラメータが多い場合や、タイトなループでメソッドを呼び出す場合は、クラスのインスタンス化のオーバーヘッドが発生します。もちろん、そのようなメソッドをタイトなループで呼び出す場合、自然で、FooSettings
オブジェクトのインスタンスを再利用するのは非常に簡単です。
例のコメントで述べたように、このパターンはパブリックAPIに最適だと思います。クラスに新しいプロパティを追加することはABIの変更ではないので、このパターンを使用してメソッドのシグネチャを変更せずに新しいオプションのパラメーターを追加できます。追加の作業なしで古いコンパイル済みコードをサポートしながら、最近コンパイルされたコードにより多くのオプションを提供できます。 。
また、C#の組み込みのデフォルトメソッドパラメーターはコンパイル時の定数として扱われ、呼び出しサイトにベイクされるため、デフォルトのパラメーターは、コードが再コンパイルされるとコードでのみ使用されます。設定オブジェクトをインスタンス化することにより、呼び出し元はメソッドを呼び出すときにデフォルト値を動的にロードします。つまり、設定クラスを変更するだけでデフォルトを更新できます。したがって、このパターンを使用すると、必要に応じて、呼び出し元を再コンパイルして新しい値を確認しなくても、デフォルト値を変更できます。
new TimeSpan(2000)
ませんが、2000ミリ秒ではないことに注意してください。これは、0.2ミリ秒、つまり2秒の10,000分の1である2000の「ティック」を意味します。