これらはどちらも、コンパイル時の定数でなければならないというエラーを生成します。
void Foo(TimeSpan span = TimeSpan.FromSeconds(2.0))
void Foo(TimeSpan span = new TimeSpan(2000))
まず、コンパイル時にこれらの値を決定できない理由を誰かが説明できますか? また、オプションの TimeSpan オブジェクトのデフォルト値を指定する方法はありますか?
これらはどちらも、コンパイル時の定数でなければならないというエラーを生成します。
void Foo(TimeSpan span = TimeSpan.FromSeconds(2.0))
void Foo(TimeSpan span = new TimeSpan(2000))
まず、コンパイル時にこれらの値を決定できない理由を誰かが説明できますか? また、オプションの TimeSpan オブジェクトのデフォルト値を指定する方法はありますか?
これは、署名を変更することで非常に簡単に回避できます。
void Foo(TimeSpan? span = null) {
if (span == null) { span = TimeSpan.FromSeconds(2); }
...
}
詳しく説明する必要があります-例のこれらの式がコンパイル時の定数ではない理由は、コンパイル時にコンパイラが単に TimeSpan.FromSeconds(2.0) を実行して、結果のバイトをコンパイル済みコードに貼り付けることができないためです。
例として、代わりに DateTime.Now を使用しようとした場合を考えてみましょう。DateTime.Now の値は、実行されるたびに変化します。または、TimeSpan.FromSeconds が重力を考慮したとします。これはばかげた例ですが、TimeSpan.FromSeconds が決定論的であることをたまたま知っているからといって、コンパイル時の定数の規則が特別なケースになるわけではありません。
私のVB6の遺産は、「null値」と「欠損値」を同等と見なすという考えに不安を感じさせます. ほとんどの場合、おそらく問題ありませんが、意図しない副作用が発生したり、例外的な条件を飲み込んだりする可能性があります (たとえば、のソースspan
が null であってはならないプロパティまたは変数である場合)。
したがって、メソッドをオーバーロードします。
void Foo()
{
Foo(TimeSpan.FromSeconds(2.0));
}
void Foo(TimeSpan span)
{
//...
}
これはうまくいきます:
void Foo(TimeSpan span = default(TimeSpan))
ノート:default(TimeSpan) == TimeSpan.Zero
デフォルト値として使用できる値のセットは、属性引数に使用できる値と同じです。その理由は、デフォルト値が .xml 内のメタデータにエンコードされているためですDefaultParameterValueAttribute
。
コンパイル時に決定できない理由について。コンパイル時に許可される値とそのような値に対する式のセットは、公式のC# 言語仕様にリストされています。
C# 6.0 - 属性パラメーターの型:
属性クラスの位置パラメータと名前付きパラメータのタイプは、次の属性パラメータ タイプに限定されます。
- 次のいずれかのタイプ:
bool
、byte
、char
、double
、float
、int
、long
、sbyte
、short
、。string
uint
ulong
ushort
- タイプ
object
。- タイプ
System.Type
。- 列挙型。
(パブリック アクセシビリティがあり、ネストされている型 (存在する場合) もパブリック アクセシビリティがある場合)- 上記の型の 1 次元配列。
型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
値が関数の有効な値である可能性がまれであることを考えると、これはより良いはずです。
TimeSpan
andの特殊なケースでありDefaultValueAttribute
、メソッドを介して解析できる任意の文字列を使用して指定されますTimeSpan.Parse
。
[DefaultValue("0:10:0")]
public TimeSpan Duration { get; set; }
他の回答では、オプションのパラメーターを動的式にできない理由について、優れた説明がありました。ただし、繰り返しになりますが、既定のパラメーターはコンパイル時の定数のように動作します。つまり、コンパイラはそれらを評価して答えを出すことができなければなりません。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
これは、この問題を解決するための非常に重いアプローチです。手早く汚い内部インターフェイスを書いていて、null を許可してTimeSpan
、null を目的のデフォルト値のように扱うとうまくいく場合は、代わりにそれを行ってください。
また、多数のパラメーターがある場合、またはタイトなループでメソッドを呼び出している場合は、クラスのインスタンス化のオーバーヘッドが発生します。もちろん、そのようなメソッドをタイトなループで呼び出す場合、FooSettings
オブジェクトのインスタンスを再利用するのは自然で、非常に簡単ですらあります。
例のコメントで述べたように、このパターンはパブリック API に最適だと思います。クラスに新しいプロパティを追加することは非破壊的な ABI の変更であるため、このパターンを使用してメソッドのシグネチャを変更せずに新しいオプションのパラメーターを追加できます — 最近コンパイルされたコードにより多くのオプションを与えながら、余分な作業なしで古いコンパイル済みコードをサポートし続けます。 .
また、C# に組み込まれている既定のメソッド パラメーターはコンパイル時の定数として扱われ、呼び出しサイトに組み込まれるため、既定のパラメーターは、再コンパイルされたコードでのみ使用されます。設定オブジェクトをインスタンス化することにより、呼び出し元はメソッドを呼び出すときにデフォルト値を動的に読み込みます。これは、設定クラスを変更するだけでデフォルトを更新できることを意味します。したがって、このパターンを使用すると、必要に応じて呼び出し元を再コンパイルして新しい値を確認することなく、デフォルト値を変更できます。