string插值问题
我想弄清楚为什么我的unit testing失败(下面的第三个断言):
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}}}");
AFAIK,这应该正常工作,但它似乎没有正确传递格式参数,它显示为{countdown|o}
的代码。 任何想法为什么这是失败?
这一行的问题
Assert.AreEqual(formatted, $"{{countdown|{date:o}}}");
是在variables的format string
之后有3个引号,并且它从左到右开始转义,因此它将前2个引号作为格式string的一部分,将第三个引号作为最后一个引号。
所以它在o}
转换o
而且它不能插入它。
这应该工作
Assert.AreEqual(formatted, $"{{countdown|{date:o}"+"}");
请注意,更简单的$"{date}}}"
(即,3在没有 format string
的variables之后curl)确实起作用,因为它认识到第一个curl引用是closures引用,而在break之后的格式说明符的解释正确的右括号标识。
为了certificate格式string像string一样被转义 ,请考虑以下内容
$"{date:\x6f}"
被视为
$"{date:o}"
最后,双转义引号是自定义date格式的一部分,所以编译器的行为是绝对合理的。 再一次,一个具体的例子
$"{date:MMM}}dd}}yyy}" // it's a valid feb}09}2017
parsing是一个基于expression式语法规则的正式过程,不能仅仅通过一眼就能完成。
这是我原来的答案的后续行动
以确保这是预期的行为
就官方来源而言,我们应该参考msdn的内插string 。
内插string的结构是
$ " <text> { <interpolation-expression> <optional-comma-field-width> <optional-colon-format> } <text> ... } "
并且每个单个插值用语法forms化地定义
single-interpolation: interpolation-start interpolation-start : regular-string-literal interpolation-start: expression expression , expression
这里重要的是这个
-
optional-colon-format
被定义为一个regular-string-literal
语法=>即它可以包含一个escape-sequence
,根据paragraph 2.4.4.5 String literals
C#语言规范5.0 - 您可以在任何可以使用
string literal
地方使用插入的string - 要在插入的string中包含大括号(
{
或}
),请使用两个花括号,{{
或}}
=>即编译器以optional-colon-format
转义两个大括号 - 编译器将包含的插值
expressions
扫描为平衡文本,直到find逗号,冒号或closures大括号=>即冒号打破平衡文本以及closures大括号
为了清楚起见,这解释了$"{{{date}}}"
之间的区别, date
是一个expression
,所以它被标记化,直到第一个花括号与$"{{{date:o}}}"
where date
又是一个expression
,现在它被标记化直到第一个冒号,之后一个常规string文字开始,编译器恢复转义两个大括号等。
还有msdn的string格式化常见问题解答 ,在这里明确地处理了这种情况。
int i = 42; string s = String.Format(“{{{0:N}}}”, i); //prints '{N}'
问题是,为什么最后一次尝试失败? 为了理解这个结果,你需要知道两件事:
提供格式说明符时,string格式化将执行以下步骤:
确定说明符是否长于单个字符:如果是,则假定说明符是自定义格式。 自定义格式将使用适合的replace格式,但是如果它不知道如何处理某些字符,则会将其作为格式中的文本进行简单地写出。确定单个字符说明符是否是受支持的说明符(例如作为数字格式的“N”)。 如果是,则格式适当。 如果不是,则抛出一个
ArgumnetException
当试图确定一个大括号是否应该被转义时,大括号只是按照他们收到的顺序来处理。 因此,
{{{
将前两个字符转义并打印文字{
,第三个括号将开始格式化部分。 在此基础上,在}}}
前两个大括号将被转义,因此一个文字}
将被写入格式化string,然后最后一个大括号将被认为是结束一个格式化部分有了这个信息,我们现在可以弄清楚我们的{{{0:N}}}
情况正在发生什么。 前两个花括号被转义,然后我们有一个格式化部分。 但是,在closures格式化部分之前,我们还要跳过结束的大括号。 因此,我们的格式部分实际上被解释为包含0:N}
。 现在,格式化程序查看格式说明符,并且它看到指定符N}
。 因此,它将其解释为自定义格式,既然N或}对于自定义数字格式都没有任何意义,这些字符只是简单地写出来,而不是被引用的variables的值。
问题似乎是插入一个括号,而使用string插值,你需要通过复制它来逃避它。 如果添加用于插值的括号本身,我们最终会得到一个三元括号,例如您在行中给出exception的那个:
Assert.AreEqual(formatted, $"{{countdown|{date:o}}}");
现在,如果我们观察到“ }}}” ,我们可以注意到第一个括号包含了string插值,而最后两个意味着被视为string转义的括号字符。
然而,编译器将前两个视为已扫描的string字符,因此它将在插值分隔符之间插入一个string。 基本上编译器是这样做的:
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正常工作的最简单的方法。
Assert.AreEqual(formatted, "{" + $"countdown|{date:o}" + "}");
在这种forms…
Assert.AreEqual(formatted, $"{{countdown|{date:o}}}");
前两个右花括号被解释为一个文字大括号,第三个则是closures格式化expression式。
这不像插入string的语法限制那么多。 错误,如果有的话,格式化文本的输出应该是“o”而不是“o”。
在C,C#和C ++中,我们有运算符“+ =”而不是“= +”的原因是在forms= +中,在某些情况下,不能说“+”是运算符的一部分还是一元“ +”。