拆分长内插字符串

Split long interpolated string

一个例子:

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}";

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

问:可以吗?拆分长内插字符串的其他选项是什么?

what this will be multiple calls to string.Format and I think it will

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

is it ok to do?

我觉得没问题

What are other options to split long interpolated string?

我会使用逐字内插字符串。这样可以很好的解决你的问题。

(因为那是你在问题中提到的 link 我不是 100% 清楚你为什么问这个问题,因为你已经阅读了一个建议好的答案的页面。)

I don't like $@ idea, it makes it worse than long string

你之前可能已经说过了。

can't it be accidentally damaged by reformatting sources?

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

What are other options to split long interpolated string?

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

编译器是做什么的?

让我们从这里开始:

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 is a black box for me yet

为什么不简单地打开它呢?使用 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 中是对象,但它们是 'special ones'。您可以从有一个特殊的 ldstr 操作码的事实中看出这一点,而且如果您查看如果您对字符串 switch 会发生什么——编译器将生成一个字典。因此,从这里您可以推断出编译器 'knows' a string 是如何在内部工作的。让我们看看它是否知道如何进行连接,好吗?

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

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

L_0000: ldstr "foobar"

tl;dr: 所以,无论内插字符串的连接是否已经实现(它们没有),我都非常有信心编译器会处理这种情况最终。

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 编译(找出优化)和执行更少时间(运行 发出的汇编程序).剩下的问题是:会发生什么?

启动程序,在写入行上放置一个断点,然后按 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 中的字符串是高度优化的东西。我们都知道它们被广泛使用,微软也是如此。如果您没有写 'really stupid code',那么可以非常合理地假设它会执行得很好(除非另有证明)。

备选方案?

What are other options to split long interpolated string?

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

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

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);