一个例子:
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对我来说是一个黑盒子)。
问题:可以吗?拆分长插值字符串有哪些其他选项?
答案 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);