【问题标题】:addition of strings in c#, how the compiler does it?在c#中添加字符串,编译器是怎么做的?
【发布时间】:2011-01-21 10:42:34
【问题描述】:
A = string.Concat("abc","def") 

B = "abc" + "def"

A 与 B

最近我一直很困惑,为什么很多人会说 A 肯定比 B 的处理速度快得多。但是,问题是他们会说是因为有人这么说或者因为它就是这样。我想我可以从这里听到更好的解释。

编译器如何处理这些字符串?

谢谢!

【问题讨论】:

  • 这种大小的字符串,没关系

标签: c# string process string-concatenation


【解决方案1】:

当我加入 C# 编译器团队时,我做的第一件事就是重写了字符串连接的优化器。好时光。

如前所述,常量字符串的字符串连接是在编译时完成的。非常量字符串可以做一些花哨的事情:

a + b --> String.Concat(a, b)
a + b + c --> String.Concat(a, b, c)
a + b + c + d --> String.Concat(a, b, c, d)
a + b + c + d + e --> String.Concat(new String[] { a, b, c, d, e })

这些优化的好处是 String.Concat 方法可以查看所有参数,确定它们的长度之和,然后制作一个可以容纳所有结果的大字符串。

这里有一个有趣的。假设你有一个返回字符串的方法 M:

s = M() + "";

如果 M() 返回 null 则结果为空字符串。 (null + empty 为空。)如果 M 不返回 null,则结果不会因空字符串的连接而改变。因此,这实际上被优化为根本不是对 String.Concat 的调用!变成了

s = M() ?? ""

整洁,嗯?

【讨论】:

    【解决方案2】:

    【讨论】:

    • 请注意:我认为很多人过分强调 StringBuilder。 .NET 中还有一个 StringWriter 类,它更容易使用,因为它的公共接口与大家从 Console 类中知道的非常相似。
    • 根据 MSDN,StringWriterStringBuilder 的包装。因此,如果StringBuilder 已经讨论过,那么在优化代码的上下文中提及StringWriter 并不重要。
    【解决方案3】:

    在 C# 中,字符串的加法运算符只是 String.Concat 的语法糖。您可以通过在反射器中打开输出组件来验证这一点。

    另外需要注意的是,如果您的代码中有字符串文字(或常量),例如在示例中,编译器甚至会将其更改为 B = "abcdef"

    但是,如果您将String.Concat 与两个字符串文字或常量一起使用,String.Concat 仍将被调用,跳过优化,因此+ 操作实际上会更快。

    所以,总结一下:

    stringA + stringB 变为 String.Concat(stringA, stringB)
    "abc" + "def" 变为 "abcdef"
    String.Concat("abc", "def") 保持不变

    我必须尝试的其他东西:

    在 C++/CLI 中,"abc" + "def" + "ghi" 实际上被翻译成String.Concat(String.Concat("abc", "def"), "ghi")

    【讨论】:

    • 没有两个字符串字面量它没有:B 将直接设置为“abcdef”。
    【解决方案4】:

    实际上,B 是在编译时解析的。你最终会得到B = "abcdef",而对于A,连接被推迟到执行时间。

    【讨论】:

    • 补充一点,当 not 遇到文字时,在字符串上使用 + 将转换为对 string.Concat() 的单个调用
    【解决方案5】:

    在这种特殊情况下,两者实际上是相同的。编译器会将第二个变体(使用 + 运算符的变体)转换为对第一个变体 Concat 的调用。

    好吧,也就是说,如果两者实际上包含串联的字符串变量。

    这段代码:

    B = "abc" + "def";
    

    实际上转换成这个,根本没有连接:

    B = "abcdef";
    

    可以这样做,因为加法的结果可以在编译时计算,所以编译器会这样做。

    但是,如果你要使用这样的东西:

    A = String.Concat(stringVariable1, stringVariable2);
    B = stringVariable1 + stringVariable2;
    

    那么这两个将生成相同的代码。

    但是,我想确切地知道那些“许多”所说的,因为我认为这是不同的东西。

    我认为他们说的是字符串连接不好,你应该使用 StringBuilder 或类似的。

    例如,如果你这样做:

    String s = "test";
    for (int index = 1; index <= 10000; index++)
        s = s + "test";
    

    然后发生的情况是,对于循环中的每次迭代,您将构建一个新字符串,并让旧字符串符合垃圾回收条件。

    此外,每个这样的新字符串都会将旧字符串的所有内容复制到其中,这意味着您将移动大量内存。

    而下面的代码:

    StringBuilder sb = new StringBuilder("test");
    for (int index = 1; index <= 10000; index++)
        sb.Append("test");
    

    将改为使用大于所需大小的内部缓冲区,以防您需要在其中添加更多文本。当该缓冲区已满时,将分配一个更大的新缓冲区,并将旧缓冲区留作垃圾回收。

    所以在内存使用和CPU使用方面,后面的变种要好得多。

    除此之外,我会尽量避免过多地关注“代码变体 X 是否比 Y 更好”,这超出了您已有的经验。例如,我现在使用 StringBuilder 只是因为我知道这种情况,但这并不是说我编写的所有使用它的代码实际上都需要它。

    尽量避免花时间对代码进行微优化,直到您知道自己遇到了瓶颈。那个时候,先量后剪的惯用技巧仍然有效。

    【讨论】:

      【解决方案6】:

      如果字符串是文字,就像您的问题一样,那么分配给B 的字符串的串联将在编译时完成。您的示例转换为:

      string a = string.Concat("abc", "def");
      string b = "abcdef";
      

      如果字符串不是文字,那么编译器会将+ 运算符转换为Concat 调用。

      所以这个...

      string x = GetStringFromSomewhere();
      string y = GetAnotherString();
      
      string a = string.Concat(x, y);
      string b = x + y;
      

      ...在编译时被翻译成这个:

      string x = GetStringFromSomewhere();
      string y = GetAnotherString();
      
      string a = string.Concat(x, y);
      string b = string.Concat(x, y);
      

      【讨论】:

        猜你喜欢
        • 2023-03-22
        • 2018-07-04
        • 1970-01-01
        • 2017-03-29
        • 1970-01-01
        • 2016-01-01
        • 2021-02-24
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多