C#の文字列補間がconst文字列で機能しないのはなぜですか?例えば:
private const string WEB_API_ROOT = "/private/WebApi/";
private const string WEB_API_PROJECT = $"{WEB_API_ROOT}project.json";
私の観点からは、すべてがコンパイル時に知られています。それとも、後で追加される機能ですか?
コンパイラメッセージ:
「DynamicWebApiBuilder.WEB_API_PROJECT」に割り当てられる式は定数でなければなりません。
どうもありがとう!
補間された文字列は、単にstring.Format
の呼び出しに変換されます。したがって、上記の行は実際に読み取ります
private const string WEB_API_PROJECT = string.Format("{0}project.json", WEB_API_ROOT);
また、メソッド呼び出しが含まれているため、これはコンパイル時定数ではありません。
一方、文字列連結(単純な定数文字列リテラルの)はコンパイラによって実行できるため、これは機能します。
private const string WEB_API_ROOT = "/private/WebApi/";
private const string WEB_API_PROJECT = WEB_API_ROOT + "project.json";
またはconst
からstatic readonly
に切り替えます:
private static readonly string WEB_API_PROJECT = $"{WEB_API_ROOT}project.json";
そのため、宣言型のメンバーへの最初のアクセス時に、文字列が初期化されます(そしてstring.Format
が呼び出されます)。
文字列補間式が定数と見なされない追加の説明は、入力がすべて定数であっても定数ではないであるということです。具体的には、それらは現在の文化に基づいて異なります。次のコードを実行してみてください。
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
Console.WriteLine($"{3.14}");
CultureInfo.CurrentCulture = new CultureInfo("cs-CZ");
Console.WriteLine($"{3.14}");
出力は次のとおりです。
3.14
3,14
文字列補間式は両方の場合で同じですが、出力は異なることに注意してください。したがって、const string pi = $"{3.14}"
、コンパイラがどのコードを生成する必要があるかは明確ではありません。
roslyn のRoslynプロジェクトで、次の結論を確定する議論があります。
抜粋を読む:
これはバグではなく、このように機能するように明示的に設計されています。あなたはそれを好きではない、それはバグになりません。文字列を連結するためにString.Formatは必要ありませんが、それはあなたがしていることではありません。あなたはそれらを補間していますが、C#で補間がどのように機能するかの仕様と実装に基づいて、String.Formatが必要です。
文字列を連結する場合は、先に進み、C#1.0以降で機能していたのと同じ構文を使用します。使用法に基づいて異なる動作をするように実装を変更すると、予期しない結果が生じます。
const string FOO = "FOO";
const string BAR = "BAR";
string foobar = $"{FOO}{BAR}";
const string FOOBAR = $"{FOO}{BAR}"; // illegal today
Debug.Assert(foobar == FOOBAR); // might not always be true
声明でさえ:
private static readonly string WEB_API_PROJECT = $"{WEB_API_ROOT}project.json";
コンパイラはエラーを発生させます:
"The name 'WEB_API_ROOT' does not exist in the current context".
変数「WEB_API_ROOT」は同じコンテキストで定義する必要があります
だから、OPの質問:文字列補間がconst文字列で動作しないのはなぜですか?回答:C#6仕様によるものです。詳細については、 。NET Compiler Platform( "Roslyn")-C#の文字列補間 を参照してください。
string.Format
で使用される定数は、その性質により、それぞれが所定の意味を持つ特定の数の引数で動作することを意図しています。
つまり、この定数を作成すると:
const string FooFormat = "Foo named '{0}' was created on {1}.";
次に、それを使用するには、おそらくstring
とDateTime
であると想定される2つの引数が必要です。
そのため、文字列補間の前でさえ、ある意味で定数を関数として使用していました。言い換えると、定数を区切る代わりに、次のように関数に入れた方が意味があったかもしれません。
string FormatFooDescription(string fooName, DateTime createdDate) =>
string.Format("Foo named '{0}' was created on {1}.", fooName, createdDate);
定数(文字列リテラル)が現在、それを使用する関数と引数と共に配置されていることを除いて、同じことです。フォーマット文字列は他の目的には役に立たないので、それらは一緒になっているかもしれません。さらに、フォーマット文字列に適用される引数の意図を確認できます。
そのように見ると、文字列補間の同様の使用が明らかになります。
string FormatFooDescription(string fooName, DateTime createdDate) =>
$"Foo named '{fooName}' was created on {createdDate}.";
複数のフォーマット文字列があり、実行時に特定のフォーマット文字列を選択したい場合はどうなりますか?
使用する文字列を選択する代わりに、関数を選択できます。
delegate string FooDescriptionFunction(string fooName, DateTime createdDate);
次に、次のような実装を宣言できます。
static FooDescriptionFunction FormatFoo { get; } = (fooName, createdDate) =>
$"Foo named '{fooName}' was created on {createdDate}.";
または、さらに良い:
delegate string FooDescriptionFunction(Foo foo);
static FooDescriptionFunction FormatFoo { get; } = (foo) =>
$"Foo named '{foo.Name}' was created on {foo.CreatedDate}.";
}