【问题标题】:Split long interpolated string拆分长插值字符串
【发布时间】:2016-06-13 22:00:01
【问题描述】:

一个例子:

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 对我来说是一个黑匣子)。

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

【问题讨论】:

  • 既然你的字符串中有换行符,为什么不在你链接的问题上使用the other answer 中的内容,但在大括号之外使用换行符?
  • “可以吗?” - 你关心纳秒还是可读性?
  • 看看使用StringBuilder
  • 我的意思是这是基于意见的。你觉得丑陋的(你的话),别人会觉得美观。如果有一种格式对您来说最易读,同时又不会显着损害性能,您为什么要关心,我们怎么能关心?
  • @FᴀʀʜᴀɴAɴᴀᴍ 因为您不希望在源代码中出现这样的字符串,而且该代码不应该通过同行评审?有很多方法可以构建字符串,而这是最丑陋的方法。我会退后一步,想知道为什么这段代码首先存在,但同样是基于意见的,所以不能真正回答。

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


【解决方案1】:

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

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

可以吗?

我觉得很好。

分割长插值字符串的其他选项是什么?

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

How do you use verbatim strings with interpolation?

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

我不喜欢 $@ 的想法,它比长字符串更糟糕

你之前可能说过。

不会因为重新格式化源而意外损坏吗?

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

分割长插值字符串的其他选项是什么?

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

【讨论】:

  • 我不喜欢$@ 的想法,就我的口味而言,它比长字符串更糟糕(我可能是错的,但它不会被重新格式化源意外损坏吗?)。我认为单个string.Format 调用比多个调用更有效,这就是我关心general 的原因。
  • @Sinatr:首先专注于代码的正确性、可读性和可维护性;当您遇到通过更改正确、可读的代码来解决的经验证明性能问题时,请担心“效率”。
  • “所有代码都可以通过更改源来更改” - 你说得对,我的意思是代码格式(例如,从一个上课到另一个)。我从不控制格式化是如何完成的,使用$@ 让我不得不这样做。
  • 我不喜欢有资源的想法。你的话听起来像是对数据源的迭代(这让我明白为什么人们说StringBuilder)。如果我必须输出所有属性或所有字典条目,那么我不会有问题。但在我的示例中,b1b2 等都是简单的局部变量。好吧,我可以先将它们的值放入某些东西(例如List<>)中......我想要的是让编译器足够聪明,并将多个$"..." +组合成单个string.Format。那么就没有问题了。
  • @EricLippert 我同意你的回答,但我发现字符串在编译时被连接起来,而插值字符串却没有(见我自己的回答)。我一直在考虑这种行为的原因(如异常行为),但没有看到它表现出这种行为的任何原因。你能想出什么好的理由(除了“还没有实现”)吗?
【解决方案2】:

编译器是做什么的?

让我们从这里开始:

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: 所以,无论内插字符串的连接是否已经实现(它们还没有实现),我都非常有信心编译器最终会处理这种情况。

JIT 做什么?

下一个问题是:使用字符串的 JIT 编译器有多聪明?

所以,让我们考虑一下,我们将教编译器了解string 的所有内部工作原理。首先我们要注意C#是编译成IL的,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 对此进行优化,假设这会花费太多时间。我相信我们可以相信 Microsoft 会明智地做出这一决定。

此外,您应该意识到 .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);

【讨论】:

  • 感谢您的精彩解释。虽然我不再喜欢{0},但在这种情况下,使用string.Format 似乎是正确的方法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-08-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多