web-dev-qa-db-ja.com

文字列補間の問題

私のユニットテストが失敗する理由を理解しようとしています(下の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}をコードに追加します。なぜこれが失敗するのか?

41

この行の問題

Assert.AreEqual(formatted, $"{{countdown|{date:o}}}");

エスケープする変数のformat stringの後に3つのカーリークォートがあり、左から右にエスケープし始めるため、最初の2つのカーリークォートはフォーマット文字列のの一部として扱われます。 と3番目のカーリークォートが最後のクオートです。

したがって、oo}に変換し、それを補間することができません。

これはうまくいくはずです

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

構文解析は、式の文法規則に基づく正式なプロセスであり、一見するだけでは実行できません。

22
user6996876

これは、元の回答へのフォローアップです。

これが意図された動作であることを確認する

公式ソースに関する限り、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  
_

ここで重要なのは

  1. _optional-colon-format_は_regular-string-literal_構文=>として定義されます。つまり、 C#言語の_escape-sequence_に従って、_paragraph 2.4.4.5 String literals_を含めることができます。仕様5.0
  2. _string literal_を使用できる場所であればどこでも、補間された文字列を使用できます。
  3. 中かっこ(_{_または_}_)を内挿文字列に含めるには、2つの中かっこ_{{_または_}}_ =>を使用します。つまり、コンパイラエスケープ _optional-colon-format_内の2つの中括弧
  4. コンパイラーは、コンマ、コロン、または中括弧が見つかるまで、含まれている補間expressionsを平衡テキストとしてスキャンします=>つまり、コロン breaks 平衡テキスト /閉じ中括弧

明確にするために、これは_$"{{{date}}}"_の違いを説明しています。ここでdateexpressionなので、最初の中括弧と_$"{{{date:o}}}"_がdateexpressionになり、最初のコロンまでトークン化されます。その後、 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も}もカスタム数値形式では何も意味しないため、参照される変数の値ではなく、これらの文字が単に書き出されます。

6
user6996876

問題は、文字列補間を使用しているときに括弧を挿入するには、それを複製してエスケープする必要があるようです。補間自体に使用される括弧を追加すると、例外を示す行にあるような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}"}}}");
2
Innat3

これは、アサートを機能させる最も簡単な方法です...

Assert.AreEqual(formatted, "{" + $"countdown|{date:o}" + "}");

この形で...

Assert.AreEqual(formatted, $"{{countdown|{date:o}}}");

最初の2つの中括弧はリテラルの閉じ中括弧として解釈され、3番目はフォーマット式を閉じるものとして解釈されます。

これは、補間された文字列の文法の制限ほどのバグではありません。バグがある場合、フォーマットされたテキストの出力はおそらく「o」ではなく「o}」になるはずです。

C、C#、およびC++で「= +」の代わりに「+ =」演算子を使用する理由は、「+」が演算子の一部であるか単項「 + "。

1
AQuirky