在 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 中声明使用字符串插值的长字符串的主要内容,如果未能解决你的问题,请参考以下文章

多行 C# 插值字符串文字

对 C# 6.0 中的字符串插值感到困惑 [重复]

C# 10 新特性 —— 插值字符串优化

C# 10 新特性 —— 补充篇

C#中字符串插值内的字符串插值导致编译器错误[重复]

如何在 TypeScript 中执行字符串插值?