views:

911

answers:

3

Hi, I've read that the StringBuilder type has a limit (the default is 16 characters), and when you append some text to it, beyond its limit, a new instance is created with a higher limit and the data is copied to it. I tried that using the following code :

StringBuilder test = new StringBuilder("ABCDEFGHIJKLMNOP",16);
test.Append("ABC");

And the CIL generated for that was :

  .maxstack  3
  .locals init (class [mscorlib]System.Text.StringBuilder V_0)
  IL_0000:  nop
  IL_0001:  ldstr      "ABCDEFGHIJKLMNOP"
  IL_0006:  ldc.i4.s   16
  IL_0008:  newobj     instance void [mscorlib]System.Text.StringBuilder::.ctor(string, int32)
  IL_000d:  stloc.0
  IL_000e:  ldloc.0
  IL_000f:  ldstr      "ABC"
  IL_0014:  callvirt   instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
  IL_0019:  pop
  IL_001a:  ret

Setting the limit to, say, 32 :

StringBuilder test = new StringBuilder("ABCDEFGHIJKLMNOP",32);
test.Append("ABC");

Generated exactly the same IL code. What I expect is creating a new instance in the first case, and changing the value of the instance in the second case, which obviously didn't happen, any clue why?

+8  A: 

All the fun stuff happens in this line:

IL_0014:  callvirt   instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)

This is where you make the call to the Append() method, but the IL you have posted does not contain the body of that method. Look in the source code for the StringBuilder class (it's released under a license that allows you to have a look), and see what happens within the Append() method.

Spoiler alert! A look at the source code of Append() will reveal that the internal buffer is indeed increased whenever the length of the concatenated string exeeds the current size of the buffer.

Jørn Schou-Rode
But the resulting CIL code shows that it's not, as I understand the CIL code a new string builder is created whenever I append text to it, just like a new string is created when you manipulate a string.
Moayad Mardini
Not sure what you mean - where in the IL are you looking? I see only one call to the constructor (IL_0008), and one to the Append() method (IL_0008). This seems to reflect your C# code very well.
Jørn Schou-Rode
NO, a new stringbuilder object is NOT created. The existing stringbuilder allocates a new BUFFER.
Joel Coehoorn
You don't get a new instance of your StringBuilder. The reallocation happens inside the StringBuilder and doesn't affect the calling code.
Per Erik Stendahl
In my understanding the OP's point was that the StringBuilder was created with the same buffer, regardless of whether he used 32 or 16 as that parameter.
hangy
In case hangy's interpretation of the is correct: have a look at the source code of the constrcutor(s) of StringBuilder. It does indeed use the provided length, when making a call to the internal string.GetStringForStringBuilder() method.
Jørn Schou-Rode
That's the answer! Thanks :)
Moayad Mardini
+1  A: 

I think that you misread the IL code. The following line:

 IL_0014:  callvirt   instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)

...does not mean that a new StringBuilder instance is created; it is merely a call to the Append method.

If calling Append would result in a string that is longer than the current capacity of the StringBuilder, it will create a new String instance internally; it will still be the same StringBuilder instance.

Fredrik Mörk
I'm very curious about how a new string instance is created internally, in fact, this is what I'm trying to understand, thanks!
Moayad Mardini
Now I understand that, thanks to Joel Mueller.
Moayad Mardini
+3  A: 

This C# code

using System.Text;

internal class Program
{
    internal static void Main(string[] args)
    {
        StringBuilder test = new StringBuilder("ABCDEFGHIJKLMNOP", 16);
        test.Append("ABC");

        StringBuilder test2 = new StringBuilder("ABCDEFGHIJKLMNOP", 32);
        test2.Append("ABC");
    }
}

produces the following IL (according to Reflector):

.class private auto ansi beforefieldinit Program
    extends [mscorlib]System.Object
{
    .method public hidebysig specialname rtspecialname instance void .ctor() cil managed
    {
        .maxstack 8
        L_0000: ldarg.0 
        L_0001: call instance void [mscorlib]System.Object::.ctor()
        L_0006: ret 
    }

    .method assembly hidebysig static void Main(string[] args) cil managed
    {
        .entrypoint
        .maxstack 3
        .locals init (
            [0] class [mscorlib]System.Text.StringBuilder test,
            [1] class [mscorlib]System.Text.StringBuilder test2)
        L_0000: nop 
        L_0001: ldstr "ABCDEFGHIJKLMNOP"
        L_0006: ldc.i4.s 0x10
        L_0008: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor(string, int32)
        L_000d: stloc.0 
        L_000e: ldloc.0 
        L_000f: ldstr "ABC"
        L_0014: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
        L_0019: pop 
        L_001a: ldstr "ABCDEFGHIJKLMNOP"
        L_001f: ldc.i4.s 0x20
        L_0021: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor(string, int32)
        L_0026: stloc.1 
        L_0027: ldloc.1 
        L_0028: ldstr "ABC"
        L_002d: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
        L_0032: pop 
        L_0033: ret 
    }
}

So here, 0x10 and 0x20 are used to initalize test and test2, which means you probably looked at the wrong IL in your test?

hangy
Thanks! What does "ldc.i4.s 0x10/0x20" means?
Moayad Mardini
0x20 (hex) = 32 dez, 0x10 (hex) = 16 dez :)I am not really familiar with IL, but I'd assume that's the constructor parameter set in HEX values.
hangy
OK, thanks a lot :)
Moayad Mardini