【问题标题】:Performance difference between C# 6.0 "string" and $"string"C# 6.0 "string" 和 $"string" 之间的性能差异
【发布时间】:2018-03-17 09:54:47
【问题描述】:

我在 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.";

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

问题:
两者之间有性能差异吗?换句话说,这两种情况是否编译为相同的?

编辑:
一些重要的 cmets 已发布,我认为重播对澄清此问题中的主题很有用。

第二个太可怕了。不要在未插值的字符串上使用字符串插值符号 ($)。其他任何查看代码的人(或未来更有经验的你)都会感到困惑

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

@communityMember1:它不会影响代码在运行时的性能。
@communityMember2:那不是真的。插值字符串是调用的语法糖 string.Format,必须被调用(从而影响 性能)。

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

【问题讨论】:

  • 第二个太可怕了。不要在未插值的字符串上使用字符串插值符号 ($)。其他任何查看代码的人(或未来更有经验的你)都会感到困惑
  • 使用 $ 是语法糖。它不会影响代码在运行时的性能。
  • @Zereges 看编译后的 IL here
  • @JonnyPiazzi "This is totally your opinion, I have a team here with 10 senior C# developers, all agreed that the second one is more legible." -- 在那种情况下我很担心
  • @BACON 我和你在一起。这个问题始于关于性能的问题,但 OP 很快就提出了哪个看起来更好。为什么还要回应人们的意见?当这不是问题的重点时,为什么还要不同意呢?最后,这个问题变成了一场关于谁喜欢什么的小便竞赛(我想 maccettura 是错的,因为 10 个高级开发人员不同意?这与性能有什么关系?) .原来的问题很好。当前的问题是关于意见。所以我投票结束。

标签: c# c#-6.0 string-interpolation syntactic-sugar


【解决方案1】:

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

编辑


性能和文字优化测试​​

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

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

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

语法

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

优点

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

缺点

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

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

【讨论】:

    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

      看看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:(IL 由 Linqpad 5.22.02 生成)。

      【讨论】:

      • 没有性能差异,但就语法而言。您绝对不应该在未插值的字符串上使用字符串插值符号,它令人困惑和愚蠢。
      【解决方案4】:

      这很容易进行一些简单的诊断。 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 的,它们是相同的
      • 我的结果显示了你在说什么。我可能不应该使用 negligible 这个词,所以我会编辑它,但我的代码只是展示了如何使用诊断功能进行测试。我可能还应该包括它如何编译以表明它并不重要
      • 感谢您指出我编辑了我的答案,如果您不介意分享,您是如何将其编译为 IL 的。
      • 酷,谢谢,以后一定要用这个!
      • 对于快速 POC 来说非常简洁,而且 IL 功能真的很酷。我不经常使用它,但在我需要 IL 的情况下,它真的很有价值(加上它是免费的)。
      猜你喜欢
      • 1970-01-01
      • 2018-02-08
      • 1970-01-01
      • 1970-01-01
      • 2020-04-20
      • 2010-11-30
      • 1970-01-01
      • 2019-06-03
      • 1970-01-01
      相关资源
      最近更新 更多