拆分长插值字符串

时间:2016-03-01 13:02:20

标签: c# code-formatting c#-6.0 string-interpolation

一个例子:

var a = $"Some value 1: {b1:0.00}\nSome value 2: {b2}\nSome value 3: {b3:0.00000}\nSome value 4: {b4:0.00}\nSome value 5: {b6:0.0}\nSome value 7: {b7:0.000000000}";

这有点难以阅读。

我可以做到

var a = $"Some value 1: {b1:0.00}\n" +
        $"Some value 2: {b2}\n" +
        $"Some value 3: {b3:0.00000}\n" +
        $"Some value 4: {b4:0.00}\n" +
        $"Some value 5: {b6:0.0}\n" +
        $"Some value 7: {b7:0.000000000}";

但是here是一条评论,说明这将是对string.Format的多次调用,我认为它会(不知道如何检查它,IL对我来说是一个黑盒子)。

问题:可以吗?拆分长插值字符串有哪些其他选项?

2 个答案:

答案 0 :(得分:7)

  

这将是对string.Format的多次调用,我认为它将

你是对的。你还没有说出你关心的原因。为什么要避免这种情况?

  

可以吗?

我很好。

  

分割长插值字符串有哪些其他选项?

我会使用逐字插值字符串。这将很好地解决你的问题。

How do you use verbatim strings with interpolation?

(由于这是你在问题中提到的链接,我不是100%明确你提出这个问题的原因,因为你已经阅读了一个建议得到一个好答案的页面。)

  

我不喜欢$ @ idea,它使它比长字符串

更糟糕

你可能早些说过。

  

重新格式化来源会不会意外损坏?

可以通过更改来源来更改所有代码。

  

分割长插值字符串有哪些其他选项?

不要先插入内插。使字符串成为资源,使类负责获取格式化的资源字符串,并隐藏如何在类的方法内格式化字符串的实现细节。

答案 1 :(得分:3)

编译器做什么?

让我们从这里开始:

var a = $"Some value 1: {b1:0.00}\n" +
        $"Some value 2: {b2}\n" +
        $"Some value 3: {b3:0.00000}\n" +
        $"Some value 4: {b4:0.00}\n" +
        $"Some value 5: {b6:0.0}\n" +
        $"Some value 7: {b7:0.000000000}";
  

IL对我来说是一个黑盒子

为什么不简单地打开它?使用ILSpy,Reflector等工具非常容易。

代码中会发生的是每行编译为string.Format。规则非常简单:如果您有$"...{X}...{Y}...",则会将其编译为string.Format("...{0}...{1}...", X, Y)。此外,+运算符将引入字符串连接。

更详细地说,string.Format是一个简单的静态调用,这意味着编译器将使用call操作码而不是callvirt

从这一切你可以推断出编译器很容易优化它:如果我们有一个像constant string + constant string + ...这样的表达式,你可以简单地用constant string替换它。您可以争辩说编译器知道string.Format的内部工作方式和字符串连接并处理它。另一方面,你可以说它不应该。让我详细说明两个考虑因素:

请注意,字符串是.NET中的对象,但它们是“特殊的”。你可以从有一个特殊的ldstr操作码的事实看到这一点,但是如果你看看如果对字符串switch会发生什么 - 编译器将生成一个字典。因此,您可以从中推断出编译器“知道”string如何在内部工作。让我们弄清楚它是否知道如何进行连接,好吗?

var str = "foo" + "bar";
Console.WriteLine(str);

在IL(当然是发布模式)中,这将给出:

L_0000: ldstr "foobar"

tl; dr:因此,无论内插字符串的连接是否已经实现(它们都不是),我相信编译器最终会处理这种情况。 / p>

JIT做什么?

接下来的问题是:带字符串的JIT编译器有多聪明?

所以,让我们暂时考虑一下,我们将教会编译器string的所有内部工作原理。首先我们应该注意C#被编译为IL,它被JIT编译为汇编程序。在switch的情况下,JIT编译器很难创建字典,所以我们必须在编译器中完成它。另一方面,如果我们处理更复杂的连接,那么使用我们已经可用于f.ex的东西是有意义的。整数运算也可以进行字符串运算。这意味着将字符串操作放在JIT编译器中。我们暂时考虑一下这个例子:

var str = "";
for (int i=0; i<10; ++i) {
    str += "foo";
}
Console.WriteLine(str);

编译器将简单地将连接编译为IL,这意味着IL将保持非常直接的实现。在这种情况下,循环展开可以说对程序的(运行时)性能有很多好处:它可以简单地展开循环,将字符串附加10次,这会产生一个简单的常量。

但是,将这些知识提供给JIT编译器会使其更加复杂,这意味着运行时将花费更多时间进行JIT编译(计算优化)并减少执行时间(运行发出的汇编程序)。剩下的问题是:会发生什么?

启动程序,在writeline上设置断点并点击ctrl-alt-D并查看汇编程序。

00007FFCC8044413  jmp         00007FFCC804443F  
            {
                str += "foo";
00007FFCC8044415  mov         rdx,2BEE2093610h  
00007FFCC804441F  mov         rdx,qword ptr [rdx]  
00007FFCC8044422  mov         rcx,qword ptr [rbp-18h]  
00007FFCC8044426  call        00007FFD26434CC0  

[...]
00007FFCC804443A  inc         eax  
00007FFCC804443C  mov         dword ptr [rbp-0Ch],eax  
00007FFCC804443F  mov         ecx,dword ptr [rbp-0Ch]  
00007FFCC8044442  cmp         ecx,0Ah  
00007FFCC8044445  jl          00007FFCC8044415  

tl; dr:没有,这还没有优化。

但我希望JIT也能优化它!

是的,好吧,我不太确定我是否同意这个观点。运行时性能与JIT编译所花费的时间之间存在平衡。请注意,如果你在紧密的循环中做这样的事情,我会争辩说你在找麻烦。另一方面,如果它是一个常见且无关紧要的情况(如连接的常量),它很容易优化,并且不会影响运行时。

换句话说:可以说,你不希望JIT优化它,假设这会花费太多时间。我相信我们可以信任微软明智地做出这个决定。

此外,您应该意识到.NET中的字符串是经过大量优化的东西。我们都知道它们被大量使用,微软也是如此。如果你不是在编写“非常愚蠢的代码”,那么它是一个非常合理的假设,它会表现得很好(除非另有证明)。

<强>替代吗

  

分割长插值字符串有哪些其他选项?

使用资源。资源是处理多种语言的有用工具。如果这只是一个小型的,非专业的项目 - 我根本不会打扰。

或者,您可以使用连接常量字符串的事实:

var fmt = "Some value 1: {1:0.00}\n" +
          "Some value 2: {2}\n" +
          "Some value 3: {3:0.00000}\n" +
          "Some value 4: {4:0.00}\n" +
          "Some value 5: {6:0.0}\n" +
          "Some value 7: {7:0.000000000}";

var a = string.Format(fmt, b1, b2, b3, b4, b5, b6, b7);