在 C# 6 中声明使用字符串插值的长字符串
Posted
技术标签:
【中文标题】在 C# 6 中声明使用字符串插值的长字符串【英文标题】:Declaring long strings that use string interpolation in C# 6 【发布时间】:2015-11-26 01:35:53 【问题描述】:我通常通过连接它们来包装长字符串:
Log.Debug("I am a long string. So long that I must " +
"be on multiple lines to be feasible.");
这是非常有效的,因为编译器处理字符串文字的连接。我也认为它是处理这个问题的最干净的方法 (the options are weighed here)。
这种方法适用于String.Format
:
Log.Debug(String.Format("Must resize 0 x 1 image " +
"to 2 x 3 for reasons.", image.Width, image.Height,
resizedImage.Width, resizedImage.Height));
但是,我现在希望在这些情况下不再使用String.Format
,因为 C# 6 的字符串插值更具可读性。 我担心的是,我不再有一种高效、干净的方式来格式化长字符串。
我的问题是编译器是否可以以某种方式优化类似
Log.Debug($"Must resize image.Width x image.Height image " +
$"to resizedImage.Width x resizedImage.Height for reasons.");
进入上述String.Format
等效项,或者如果我可以使用一种不会降低效率的替代方法(由于不必要的连接),同时还保持我的代码结构清晰(根据链接中提出的要点以上)。
【问题讨论】:
我看不出它与使用String.Format
有何不同
好吧,我确实在 LINQPad 中对其进行了测试,当使用 C# 6 的字符串插值时,它肯定会执行连接(显式使用 String.Format
时不会发生这种情况)。我主要是希望可能有另一种方法,编译器选项等。
我很惊讶格式字符串不被视为编译时常量
您可以改用逐字插值字符串 ($@"..."
)
我认为整个“字符串是不可变的,不要连接它们”的智慧已经失控。与计算中的任何“规则”一样,这一规则也有很多例外。只有在尝试进行实时(或接近实时)处理时(由于 GC 的不可预测性,你可能不应该在 C# 中这样做)或者如果你正在做几十个(更现实地是数百个) 的串联。连接五个字符串会迷失在正在发生的所有其他事情的噪音中。我不会担心它并且在可读代码方面犯错。
【参考方案1】:
这个程序:
var name = "Bobby Tables";
var age = 8;
String msg = $"I'm name and" +
$" I'm age years old";
就像你写的那样编译:
var name = "Bobby Tables";
var age = 8;
String msg = String.Concat(String.Format("I'm 0 and", name),
String.Format(" I'm 0 years old", age));
您看到了摆脱 Concat
的困难——编译器已经重写了我们的插值文字以使用 String.Format
期望的索引格式化程序,但是每个字符串必须从 0 开始对其参数进行编号。天真地连接它们会导致他们都插入name
。为了让它正确工作,必须在$
解析器的调用之间保持状态,以便将第二个字符串重新格式化为" I'm 1 years old"
。或者,编译器可以尝试应用与字符串文字串联相同的分析。我认为这将是一种合法的优化,即使字符串插值可能会产生副作用,但如果事实证明存在插值字符串连接改变程序行为的极端情况,我不会感到惊讶。听起来都不是不可能的,特别是考虑到已经存在检测字符串文字的类似条件的逻辑,但我明白为什么这个功能没有进入第一个版本。
我会以您认为最简洁、最易读的方式编写代码,除非它们被证明是一个问题,否则不要担心微小的效率低下。关于代码主要是供人类理解的老话在这里成立。
【讨论】:
这似乎只是一个问题,因为编译器没有意识到$"foo" + $"bar"
与$"foobar"
相同。如果它可以首先删除串联(在编译时),那么这种优化似乎是有意义的。虽然是的,但目前似乎不可能(需要编译器支持)。我正在使用临时解决方法,即不打破我的线路并让 VS 进行软换行。【参考方案2】:
也许它不像+
那样可读,但无论如何,这是可能的。您只需要在 和
之间换行即可:
Log.Debug($@"Must resize image.Width x image.Height image to
resizedImage.Width x resizedImage.Height for reasons.");
SO 的着色脚本不能很好地处理这种语法,但 C# 编译器可以;-)
【讨论】:
这是一种我没有想到的有趣方法。我喜欢这样,因为我们在大括号之间,我们可以在不影响字符串的情况下使用空格。【参考方案3】:在 html 中使用此字符串的特殊情况下(或使用与多个空格无关的任何解析器进行解析),我建议您使用 @$""
字符串(逐字插值字符串),例如:
$@"some veeeeeeeeeeery long string foo
whatever bar"
【讨论】:
我喜欢逐行分割的逐字字符串,但必须将它一直对齐到左边会害死我,所以我永远不会这样做。【参考方案4】:在 c# 6.0 中:
var planetName = "Bob";
var myName = "Ford";
var formattedStr = $"Hello planet planetName, my name is myName!";
// formattedStr should be "Hello planet Bob, my name is Ford!"
然后与 stringbuilder 连接:
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.Append(formattedStr);
// Then add the strings you need
将更多字符串附加到字符串生成器.....
【讨论】:
我看不出这比仅使用运算符+
更简洁,它将在 IL 中使用单个 Concat
调用(与StringBuilder
一样高效,但更具可读性) .此外,您的代码仍然存在前面提到的问题,即不必要的连接和使用多个 String.Format
调用,如果我们使用单个字符串就不会出现这种情况。
与“+”相比,StringBuilder 使用多个字符串提供更好的性能。
不一定在这种情况下,字符串在编译时是已知的。它被转换为单个String.Concat
调用。使用StringBuilder
的主要原因是避免重复连接(特别是在循环中连接时)会发生的不必要的对象创建。但是在这里我们谈论的是通常相当少量的字符串,它们恰好太长而无法干净地换行。另请参阅this question。以上是关于在 C# 6 中声明使用字符串插值的长字符串的主要内容,如果未能解决你的问题,请参考以下文章