我正在试图找出我的单元测试失败的原因(下面的第三个断言):
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}
代码。知道为什么会失败吗?
答案 0 :(得分:22)
这一行的问题
Assert.AreEqual(formatted, $"{{countdown|{date:o}}}");
是要在要转义的变量的format string
之后有3个卷曲引号并且它从左向右开始转义,因此它将前2个卷曲引号视为格式字符串的一部分< / em>和第三个卷曲的引用作为结束的。
因此它会转换o
中的o}
,并且它无法插入它。
这应该有效
Assert.AreEqual(formatted, $"{{countdown|{date:o}"+"}");
请注意,更简单的$"{date}}}"
(即变量之后没有 a format string
的3个卷曲)确实有效,因为它识别出第一个卷曲引用是结束引用,而:
打破正确的右括号标识后对格式说明符的解释。
要证明格式字符串是转义,就像字符串一样,请考虑以下内容
$"{date:\x6f}"
被视为
$"{date:o}"
最后,双重转义的卷曲引号完全有可能是自定义日期格式的一部分,因此编译器的行为绝对合理。再一次,一个具体的例子
$"{date:MMM}}dd}}yyy}" // it's a valid feb}09}2017
解析是一个基于表达式语法规则的正式过程,不能通过浏览它来完成。
答案 1 :(得分:6)
这是跟进到我原来的答案
确保这是预期的行为
就官方来源而言,我们应该参考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
语法=&gt;即根据C# Language Specification 5.0 escape-sequence
,它可以包含paragraph 2.4.4.5 String literals
string literal
{
或}
),请使用两个大括号{{
或}}
=&gt;即编译器在optional-colon-format
expressions
扫描为平衡文本,直到找到逗号,冒号或关闭大括号=&gt;即结肠打破平衡文本以及一个紧密的大括号为了清楚起见,这解释了$"{{{date}}}"
date
expression
与$"{{{date:o}}}"
之间的差异,因此在第一次大括号与date
expression
之前标记为int i = 42;
string s = String.Format(“{{{0:N}}}”, i); //prints ‘{N}’
1}}再次是ArgumnetException
,现在它被标记化,直到第一个冒号,之后常规字符串文字开始,编译器恢复转义两个花括号等...
还有来自msdn的String Formatting FAQ,其中明确处理了此案例。
{{{
问题是,为什么最后一次尝试失败了?有两件事 你需要知道才能理解这个结果:
提供格式说明符时,字符串格式化会采用这些格式说明符 步骤进行:
确定说明符是否长于单个字符:如果是, 然后假设说明符是自定义格式。自定义格式 将为您的格式使用合适的替代品,但如果它不知道 如何处理某些角色,它只会将其写成一个 以格式找到的文字确定单个字符 说明符是受支持的说明符(例如数字的“N”) 格式)。如果是,则适当格式化。如果没有,扔一个
{
尝试确定是否应该使用花括号 转义后,大括号按照它们的顺序进行简单处理 接收。因此,
}}}
将转义前两个字符和 打印文字}
,第三个花括号将开始 格式化部分。在此基础上,在{{{0:N}}}
前两个卷曲 括号将被转义,因此将写入文字0:N}
格式字符串,然后将假定最后一个大括号 结束格式化部分有了这些信息,我们现在可以 弄清楚N}
情况下发生了什么。首先 两个花括号被转义,然后我们有一个格式化部分。 但是,在关闭之前,我们还会从结束的大括号中逃脱 格式化部分。因此,我们的格式化部分实际上是 解释为包含let testText = SCNText(string: "ABCDEF", extrusionDepth: 0) testText.font = font let txtNd = SCNNode(geometry: testText) txtNd.position = SCNVector3(0,0,0) rootNode.addChildNode(txtNd) let bMax = txtNd.boundingBox.max let bMin = txtNd.boundingBox.min print("\(bMax)\n\(bMin)")
。现在,格式化程序看着 格式说明符,它看到说明符object Dummy { abstract class Foo { def execute: Unit } abstract class Bar { def execute: Unit } class FooFoo extends Foo { def execute: Unit = { println("Foo") } } class BarBar extends Bar { def execute: Unit = { println("Bar") } } def run(x: AnyRef): Unit = { x match { case f: Foo => f.execute case b: Bar => b.execute case _ => } } def main(args: Array[String]): Unit = { val any = new BarBar() run(any) } }
。因此 将此解释为自定义格式,因为N或}都不是 任何自定义数字格式,这些字符都很简单 写出来,而不是引用变量的值。
答案 2 :(得分:2)
问题似乎是在使用字符串插值时插入一个括号,你需要通过复制它来逃避它。如果你添加用于插值本身的括号,我们最终得到一个三重括号,例如你在行中提供例外的那个括号:
Assert.AreEqual(formatted, $"{{countdown|{date:o}}}");
现在,如果我们观察&#34; }}}&#34; ,我们可以注意到第一个括号包含了字符串插值,而最后两个括号被视为字符串转义的括号字符。
然而,编译器将前两个视为字符串字符,因此它在插值分隔符之间插入一个字符串。基本上编译器正在做这样的事情:string str = "a string";
$"{str'}'}"; //this would obviously generate a compile error which is bypassed by this bug
您可以通过重新格式化此行来解决此问题:
Assert.AreEqual(formatted, $"{{countdown|{$"{date:o}"}}}");
答案 3 :(得分:1)
这是让断言起作用的最简单方法......
Assert.AreEqual(formatted, "{" + $"countdown|{date:o}" + "}");
以这种形式......
Assert.AreEqual(formatted, $"{{countdown|{date:o}}}");
前两个结束括号被解释为文字右括号,第三个用于关闭格式表达式。
这不是一个错误,而是对插值字符串的语法限制。错误(如果有的话)是格式化文本的输出应该是&#34; o}&#34;而不只是&#34; o&#34;。
我们有运营商&#34; + =&#34;而不是&#34; = +&#34;在C,C#和C ++中,形式= +你在某些情况下无法判断是否&#34; +&#34;是运营商或一元&#34; +&#34;的一部分。