私のユニットテストが失敗する理由を理解しようとしています(下の3番目のアサート):
var date = new DateTime(2017, 1, 1, 1, 0, 0);
var formatted = "{countdown|" + date.ToString("o") + "}";
//Works
Assert.AreEqual(date.ToString("o"), $"{date:o}");
//Works
Assert.AreEqual(formatted, $"{{countdown|{date.ToString("o")}}}");
//This one fails
Assert.AreEqual(formatted, $"{{countdown|{date:o}}}");
私の知る限り、これは正しく機能するはずですが、フォーマットパラメータを正しく渡さないようです。単に{countdown|o}
をコードに追加します。なぜこれが失敗するのか?
この行の問題
Assert.AreEqual(formatted, $"{{countdown|{date:o}}}");
エスケープする変数のformat string
の後に3つのカーリークォートがあり、左から右にエスケープし始めるため、最初の2つのカーリークォートはフォーマット文字列のの一部として扱われます。 と3番目のカーリークォートが最後のクオートです。
したがって、o
をo}
に変換し、それを補間することができません。
これはうまくいくはずです
Assert.AreEqual(formatted, $"{{countdown|{date:o}"+"}");
シンプルな$"{date}}}"
(つまり、変数の後に3つのカール(_/S/CODE)なしa format string
)が機能することに注意してください。一方、:
の後のフォーマット指定子の解釈は、正しい右括弧の識別を壊します。
フォーマット文字列がエスケープされていることを証明するには文字列のように、次のことを考慮してください
$"{date:\x6f}"
として扱われます
$"{date:o}"
最後に、二重にエスケープされた中括弧がカスタム日付フォーマットの一部である可能性が完全にあるため、コンパイラーの動作は完全に合理的です。繰り返しますが、具体的な例
$"{date:MMM}}dd}}yyy}" // it's a valid feb}09}2017
構文解析は、式の文法規則に基づく正式なプロセスであり、一見するだけでは実行できません。
これは、元の回答へのフォローアップです。
これが意図された動作であることを確認する
公式ソースに関する限り、msdnの Interpolated Strings を参照する必要があります。
補間された文字列の構造は次のとおりです。
_$ " <text> { <interpolation-expression> <optional-comma-field-width> <optional-colon-format> } <text> ... } "
_
そして、各単一の補間は構文で正式に定義されています
_single-interpolation:
interpolation-start
interpolation-start : regular-string-literal
interpolation-start:
expression
expression , expression
_
ここで重要なのは
optional-colon-format
_は_regular-string-literal
_構文=>として定義されます。つまり、 C#言語の_escape-sequence
_に従って、_paragraph 2.4.4.5 String literals
_を含めることができます。仕様5.0string literal
_を使用できる場所であればどこでも、補間された文字列を使用できます。{
_または_}
_)を内挿文字列に含めるには、2つの中かっこ_{{
_または_}}
_ =>を使用します。つまり、コンパイラエスケープ _optional-colon-format
_内の2つの中括弧expressions
を平衡テキストとしてスキャンします=>つまり、コロン breaks 平衡テキスト /閉じ中括弧明確にするために、これは_$"{{{date}}}"
_の違いを説明しています。ここでdate
はexpression
なので、最初の中括弧と_$"{{{date:o}}}"
_がdate
もexpression
になり、最初のコロンまでトークン化されます。その後、 regular string literal が始まり、コンパイラーが2つの中括弧などをエスケープします。 ...
Msdnの String Formatting FAQ もあり、このケースは明示的に扱われました。
_int i = 42;
string s = String.Format(“{{{0:N}}}”, i); //prints ‘{N}’
_
問題は、なぜこの最後の試みが失敗したのかということです。この結果を理解するには、2つのことを知っておく必要があります。
フォーマット指定子を提供する場合、文字列フォーマットは次の手順を実行します。
指定子が単一の文字よりも長いかどうかを判別します。長い場合は、指定子がカスタム形式であると想定します。カスタムフォーマットは、フォーマットの適切な置換を使用しますが、一部の文字の処理方法がわからない場合は、フォーマットで見つかったリテラルとして書き出すだけです。数値のフォーマットの場合は「N」として)。正しい場合は、適切にフォーマットします。そうでない場合は、
ArgumnetException
をスローします中かっこをエスケープする必要があるかどうかを判断する場合、中かっこは受信した順に処理されます。したがって、_
{{{
_は最初の2文字をエスケープしてリテラル_{
_を出力し、3番目の中括弧はフォーマットセクションを開始します。これに基づいて、_}}}
_では最初の2つの中かっこがエスケープされます。したがって、リテラル_}
_がフォーマット文字列に書き込まれ、最後の中かっこがフォーマットセクションを終了すると見なされます。この情報を使用して、_{{{0:N}}}
_状況で何が起こっているかを把握できます。最初の2つの中かっこがエスケープされ、次にフォーマットセクションがあります。ただし、フォーマットセクションを閉じる前に、閉じ中かっこもエスケープします。したがって、フォーマットセクションは実際には_0:N}
_を含むものとして解釈されます。これで、フォーマッターはフォーマット指定子を調べ、指定子の_N}
_を確認します。したがって、これはカスタム形式として解釈され、Nも}もカスタム数値形式では何も意味しないため、参照される変数の値ではなく、これらの文字が単に書き出されます。
問題は、文字列補間を使用しているときに括弧を挿入するには、それを複製してエスケープする必要があるようです。補間自体に使用される括弧を追加すると、例外を示す行にあるような3つの括弧が作成されます。
Assert.AreEqual(formatted, $"{{countdown|{date:o}}}");
ここで、「}}}」を確認すると、最初の括弧が文字列補間を囲んでいることがわかりますが、最後の2つは文字列をエスケープした括弧文字として扱われます。
ただし、コンパイラは最初の2つをエスケープ文字列文字として処理しているため、補間区切り文字の間に文字列を挿入しています。基本的にコンパイラは次のようなことをしています:
string str = "a string";
$"{str'}'}"; //this would obviously generate a compile error which is bypassed by this bug
この問題を解決するには、次のように行を再フォーマットします。
Assert.AreEqual(formatted, $"{{countdown|{$"{date:o}"}}}");
これは、アサートを機能させる最も簡単な方法です...
Assert.AreEqual(formatted, "{" + $"countdown|{date:o}" + "}");
この形で...
Assert.AreEqual(formatted, $"{{countdown|{date:o}}}");
最初の2つの中括弧はリテラルの閉じ中括弧として解釈され、3番目はフォーマット式を閉じるものとして解釈されます。
これは、補間された文字列の文法の制限ほどのバグではありません。バグがある場合、フォーマットされたテキストの出力はおそらく「o」ではなく「o}」になるはずです。
C、C#、およびC++で「= +」の代わりに「+ =」演算子を使用する理由は、「+」が演算子の一部であるか単項「 + "。