在c#中添加字符串,编译器如何做到这一点?

时间:2011-01-21 10:42:34

标签: c# string process string-concatenation

A = string.Concat("abc","def") 

B = "abc" + "def"

A vs. B

最近我很困惑为什么很多人会说A与B相比肯定会做得更快。但是,他们只会说因为有人这么说或者因为它就是这样。我想我可以从这里听到更好的解释。

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

谢谢!

6 个答案:

答案 0 :(得分:12)

当我加入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() ?? ""

整洁,嗯?

答案 1 :(得分:5)

在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")

答案 2 :(得分:5)

答案 3 :(得分:1)

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

答案 4 :(得分:1)

如果字符串是文字,就像你的问题一样,那么分配给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);

答案 5 :(得分:1)

在这种特殊情况下,两者实际上是相同的。编译器会将第二个变量(使用+运算符的变量)转换为对第一个变体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只是因为我知道这种情况,但这并不是说我编写的所有使用它的代码实际上都需要它。

尽量避免花时间微优化代码,直到您知道自己有瓶颈为止。那个时候,关于测量的通常的提示,后来削减,仍然有效。