C# 6.0 "string" 和 $"string" 之间的性能差异

Performance difference between C# 6.0 "string" and $"string"

我在 C# 6.0 中有 2 个代码:

示例 1:

string bar;
// ... some code setting bar.
var foo =
    "Some text, " +
    $"some other {bar}, " +
    "end text.";

示例 2:

string bar;
// ... some code setting bar.
var foo =
    $"Some text, " +
    $"some other {bar}, " +
    $"end text.";

显然,无论审美差异如何,两种代码都会生成相同的结果。

问题:
两者之间有性能差异吗?换句话说,两种情况编译成一样吗?

编辑:
发布了一些重要的评论,我发现重播这些评论有助于澄清这个问题中的主题。

The second one is horrible. Don't use the string interpolation symbol ($) on strings that aren't interpolated. It will be confusing for anyone else looking at the code (or future, more experienced you)

感谢您的提示,非常感谢,尽管我不得不争辩说您所说的是美学论证。即使这段代码像你说的那样丑陋,即使永远不会被使用,我仍然认为讨论它可能产生的可能的性能差异是有用的。

@communityMember1: It doesn't affect performance of the code at runtime.
@communityMember2: That is not true. Interpolated string is syntactic sugar for calling string.Format, which has to be called (and thus affecting performance).

也许为了避免进一步的讨论,我们应该根据一些文档甚至是经验证明(比如汇编的结果)来加强答案和评论。

$ 创建一个内插字符串,与使用 String.Format 相同。如果您将 + 与字符串文字一起使用,则编译器会优化代码以避免将它们串联在一起。使用内插字符串可能会阻止此优化。

编辑


性能和文字优化测试​​

好的,我刚刚对此进行了测试,似乎没有任何性能问题。显然,编译器会忽略 $,如果没有 {},则不会使用 String.Format。我的测试结果如下所示,用于构造一个 50 长度的字符串的 600 万个循环。

  • String.Format:23700 毫秒
  • $"a{null}"+:22650 毫秒
  • $"a"+:13 毫秒
  • "a"+:13 毫秒
  • "a"+ 连接:700 毫秒

此外,查看 IL 也没有区别。因此在性能方面,您发布的代码的执行和优化相同, $ 被忽略并且仅在使用 {} 时成为一个因素。

语法

当然可以争论这是否是好的语法。我能想到支持和反对的论据。

优点

  • 如果 $ 已经存在,添加新参数会更容易
  • 在多行文字的行之间移动参数更容易
  • 它提供多行文字的行之间的一致性

缺点

  • 它看起来不一样... 最不惊讶的原则。
  • 为什么要添加未使用的语法?
  • 插入的变量不明显

对我来说,倾斜点是我看到 $ 并期望看到 {} 和一个论点。如果我没有看到我期望看到的东西,就会导致认知失调。另一方面,我可以想象它提供的流动性可能超出此范围的情况。说它取决于开发组和目标可能还很远。

这很容易进行一些简单的诊断。 Maccettura 在评论中表明,编译时没有区别,因为它只为每个字符串调用 String.Format 一次,而不管额外的插值,但此测试也会对其进行备份。

int rounds = 50;
int timesToCreateString = 50000;
Stopwatch sw = new Stopwatch();
double first = 0;
double second = 0;
for (int i = 0; i < rounds; i++)
{
    sw.Start();
    for (int bar = 0; bar < timesToCreateString; bar++)
    {
        var foo = "Some text, " +
                 $"some other {bar}, " +
                  "end text.";
    }
    sw.Stop();
    first += sw.ElapsedTicks;
    sw.Reset();
    sw.Start();
    for (int bar = 0; bar < timesToCreateString; bar++)
    {                
        var foo = $"Some text, " +
                  $"some other {bar}, " +
                  $"end text.";
    }
    sw.Stop();
    second += sw.ElapsedTicks;
    sw.Reset();
}
Console.WriteLine("Average first test: " + first / rounds);
Console.WriteLine("Average second test: " + second / rounds);
Console.ReadKey();
// program ran 3 times and results.
//Average first test: 54822.04
//Average second test: 55083.86

//Average first test: 54317.66
//Average second test: 54807.8

//Average first test: 49873.12
//Average second test: 48264.36

查看IL:

第一个密码:

var bar = "a";
var foo = "b" + $"{bar}";

第一个IL:

IL_0000:  nop         
IL_0001:  ldstr       "a"
IL_0006:  stloc.0     // bar
IL_0007:  ldstr       "b"
IL_000C:  ldstr       "{0}"
IL_0011:  ldloc.0     // bar
IL_0012:  call        System.String.Format
IL_0017:  call        System.String.Concat
IL_001C:  stloc.1     // foo
IL_001D:  ret         

第二个密码:

var bar = "a";
var foo = $"b" + $"{bar}";

第二个IL:

IL_0000:  nop         
IL_0001:  ldstr       "a"
IL_0006:  stloc.0     // bar
IL_0007:  ldstr       "b"
IL_000C:  ldstr       "{0}"
IL_0011:  ldloc.0     // bar
IL_0012:  call        System.String.Format
IL_0017:  call        System.String.Concat
IL_001C:  stloc.1     // foo
IL_001D:  ret         

结论:

两个代码之间没有编译差异。

PS: (Linqpad 5.22.02 生成的IL).

你可以选择任何你喜欢的。 $ 确实会转换为 string.format(请参阅 https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/interpolated-strings),但编译器足够聪明,可以过滤掉未格式化的代码。