字符串连接是否在内部使用StringBuilder?

时间:2010-05-20 19:01:30

标签: c# compiler-construction string concatenation

我的三个同事告诉我,没有理由使用StringBuilder代替使用+运算符进行连接。换句话说,这对于一堆字符串很好:myString1 + myString2 + myString3 + myString4 + mySt...

他们使用的基本原理是,自.NET 2起,如果使用+运算符,C#编译器将构建相同的IL,就像使用StringBuilder一样。

这对我来说是新闻。它们是否正确?

8 个答案:

答案 0 :(得分:25)

不,他们不正确。字符串连接创建新的string,而StringBuilder使用可变大小的缓冲区来构建字符串,仅在调用ToString()时创建string对象。

如果您想进一步阅读有关该主题的内容,有很多关于字符串连接技术的讨论。大多数注重在循环中使用时不同方法的效率。在这种情况下,StringBuilder使用concatenations of 10 or more strings的字符串运算符比字符串连接更快,这应该表明它必须使用与串联不同的方法。

也就是说,如果你连接常量字符串值,字符串运算符会更好,因为编译器会将它们分解,如果执行非循环连接,使用运算符会更好,因为它们应该导致单次拨打string.Concat

答案 1 :(得分:16)

不,它们不正确,它不会产生相同的IL:

static string StringBuilder()
{
    var s1 = "s1";
    var s2 = "s2";
    var s3 = "s3";
    var s4 = "s4";
    var sb = new StringBuilder();
    sb.Append(s1).Append(s2).Append(s3).Append(s4);
    return sb.ToString();
}

static string Concat()
{
    var s1 = "s1";
    var s2 = "s2";
    var s3 = "s3";
    var s4 = "s4";
    return s1 + s2 + s3 + s4;
}

String of StringBuilder:

.method private hidebysig static string StringBuilder() cil managed
{
    .maxstack 2
    .locals init (
        [0] string s1,
        [1] string s2,
        [2] string s3,
        [3] string s4,
        [4] class [mscorlib]System.Text.StringBuilder sb)
    L_0000: ldstr "s1"
    L_0005: stloc.0 
    L_0006: ldstr "s2"
    L_000b: stloc.1 
    L_000c: ldstr "s3"
    L_0011: stloc.2 
    L_0012: ldstr "s4"
    L_0017: stloc.3 
    L_0018: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor()
    L_001d: stloc.s sb
    L_001f: ldloc.s sb
    L_0021: ldloc.0 
    L_0022: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
    L_0027: ldloc.1 
    L_0028: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
    L_002d: ldloc.2 
    L_002e: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
    L_0033: ldloc.3 
    L_0034: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
    L_0039: pop 
    L_003a: ldloc.s sb
    L_003c: callvirt instance string [mscorlib]System.Object::ToString()
    L_0041: ret 
}

IL of Concat:

.method private hidebysig static string Concat() cil managed
{
    .maxstack 4
    .locals init (
        [0] string s1,
        [1] string s2,
        [2] string s3,
        [3] string s4)
    L_0000: ldstr "s1"
    L_0005: stloc.0 
    L_0006: ldstr "s2"
    L_000b: stloc.1 
    L_000c: ldstr "s3"
    L_0011: stloc.2 
    L_0012: ldstr "s4"
    L_0017: stloc.3 
    L_0018: ldloc.0 
    L_0019: ldloc.1 
    L_001a: ldloc.2 
    L_001b: ldloc.3 
    L_001c: call string [mscorlib]System.String::Concat(string, string, string, string)
    L_0021: ret 
}

另外,您可能会发现this article很有趣。

答案 2 :(得分:5)

不,他们不是。他们肯定产生不同的IL。它在非String.Concat案例中使用了不同的来电:StringBuilder

String.Concat调用一个名为ConcatArray的私有方法,该方法分配一个新字符串,足以保存最终结果。所以,非常不同,但这并不意味着使用+运算符连接的效率低于使用StringBuilder。事实上,它几乎肯定更有效率。此外,在连接常量的情况下,它在编译时完成。

但是,当您在循环中进行连接时,编译器无法执行此类优化。在这种情况下,使用StringBuilder对于相当长的字符串会更好。

答案 3 :(得分:4)

答案是它取决于你如何连接。如果你使用带有静态字符串的+运算符,那么你的朋友是正确的 - 不需要字符串构建器。但是,如果您使用字符串变量或+ =运算符,那么您将重新分配字符串。

真正了解这里发生了什么的方法是编写一些代码然后反编译。

让我们构建一些测试代码并使用IL视图在Reflector中查看它(或者你可以使用ILDASM,无论你喜欢哪个

首先,基线 - 此方法根本不连接:


static void NoConcat()
{
  string test = "Hello World";
}

现在是IL:


.method private hidebysig static void NoConcat() cil managed
{
    .maxstack 1
    .locals init (
        [0] string test)
    L_0000: nop 
    L_0001: ldstr "Hello World"  <----------NO reallocation!
    L_0006: stloc.0 
    L_0007: ret 
}

好的,没有惊喜,对吧?

现在让我们看看一些肯定会重新分配字符串的代码,所以我们知道它是什么样的:


static void Concat2()
{
  string test = "Hello";
  test += " ";
  test += "World";
}

这是IL,请注意重新分配(它调用string.Concat,这会导致分配一个新字符串):


.method private hidebysig static void Concat2() cil managed
{
    .maxstack 2
    .locals init (
        [0] string test)
    L_0000: nop 
    L_0001: ldstr "Hello"
    L_0006: stloc.0 
    L_0007: ldloc.0 
    L_0008: ldstr " "
    L_000d: call string [mscorlib]System.String::Concat(string, string)
    L_0012: stloc.0 
    L_0013: ldloc.0 
    L_0014: ldstr "World"
    L_0019: call string [mscorlib]System.String::Concat(string, string)
    L_001e: stloc.0 
    L_001f: ret 
}

好的,现在如何进行不会导致重新分配的串联 - 我们将使用“+”运算符连接静态字符串:


static void Concat1()
{
  string test = "Hello" + " " + "World";
}

这是IL - 看看编译器有多聪明!它不使用concat - 它与第一个例子相同:


.method private hidebysig static void Concat1() cil managed
{
    .maxstack 1
    .locals init (
        [0] string test)
    L_0000: nop 
    L_0001: ldstr "Hello World"
    L_0006: stloc.0 
    L_0007: ret 
}

现在让我们玩得开心吧。如果我们混合静态字符串和变量怎么办? (这是你使用字符串制作者可能还会更好的地方)


static void Concat3(string text)
{
  string test = "Hello" + " " + text + " World";
}

和IL。请注意,将“Hello”和“”组合为常量非常智能,但仍需要对文本变量进行连续处理:


.method private hidebysig static void Concat3(string text) cil managed
{
    .maxstack 3
    .locals init (
        [0] string test)
    L_0000: nop 
    L_0001: ldstr "Hello "
    L_0006: ldarg.0 
    L_0007: ldstr " World"
    L_000c: call string [mscorlib]System.String::Concat(string, string, string)
    L_0011: stloc.0 
    L_0012: ret 
}

答案 4 :(得分:1)

我通常遵循以下规则:

  1. 如果子字符串的数量是预先知道的,请使用连接。这涵盖了str1 + str2 + str3 + ......等情况,无论它们有多少。

  2. 如果子字符串已在数组中,请使用string.join

  3. 如果在循环中构建字符串,请使用StringBuilder

答案 5 :(得分:0)

String和StringBuilder之间略有不同:

连接字符串将创建一个新的字符串对象,该字符串对象是串联的结果。连接StringBuilder会修改字符串对象。

所以他们不正确。

答案 6 :(得分:0)

字符串连接和StringBuidler之间存在巨大的性能差异。我们的网络服务太慢了。我们将所有的字符串猫改为StringBuilder.Appends并且速度更快了!

答案 7 :(得分:0)

不,字符串连接在内部不使用StringBuilder。但是,在您的特定示例中,使用StringBuilder没有任何优势。

这适用于几个字符串(您只创建一个新字符串):

myString = myString + myString2 + myString3 + myString4 + mySt...

这不是(你正在创建和分配4个字符串等):

myString = myString + myString2;
myString = myString + myString3;
myString = myString + myString4;
myString = myString + myString5;

在关于此问题的所有stackoverflow问题中,这有一个最好的答案: String vs. StringBuilder

寻找两个答案,一个是Jay Bazuzi,另一个是James Curran。

另外,强烈建议,Jeff Atwood使用实际测试来比较字符串连接/构建的这些和其他场景,这里: http://www.codinghorror.com/blog/2009/01/the-sad-tragedy-of-micro-optimization-theater.html