String Concatenation vs Memory Allocation

by David Cumps

Solution used for testing

Method returning a string. No allocation seems to take place (maybe 1).


.method public hidebysig instance string GetPlussedString() cil managed
{
    .maxstack 1
    .locals init (
        [0] string myString)
    L_0000: ldstr "SELECT column1,       column2,       column3,       column4,       column5,       column6,  FROM table1 t1  JOIN table2 t2    ON t1.column1 = t2.column1"
    L_0005: stloc.0 
    L_0006: ldloc.0 
    L_0007: ret 
}

Method += concatenating 9 strings. Translates into 8 String.Concat calls.
Uses 16 more String instances than our base.
(8 due to Concat and 8 extra strings in code)


.method public hidebysig instance string GetPlussedVarString() cil managed
{
    .maxstack 2
    .locals init (
        [0] string myString)
    L_0000: ldstr "SELECT column1,"
    L_0005: stloc.0 
    L_0006: ldloc.0 
    L_0007: ldstr "       column2,"
    L_000c: call string [mscorlib]System.String::Concat(string, string)
    L_0011: stloc.0 
    L_0012: ldloc.0 
    L_0013: ldstr "       column3,"
    L_0018: call string [mscorlib]System.String::Concat(string, string)
    L_001d: stloc.0 
    L_001e: ldloc.0 
    L_001f: ldstr "       column4,"
    L_0024: call string [mscorlib]System.String::Concat(string, string)
    L_0029: stloc.0 
    L_002a: ldloc.0 
    L_002b: ldstr "       column5,"
    L_0030: call string [mscorlib]System.String::Concat(string, string)
    L_0035: stloc.0 
    L_0036: ldloc.0 
    L_0037: ldstr "       column6,"
    L_003c: call string [mscorlib]System.String::Concat(string, string)
    L_0041: stloc.0 
    L_0042: ldloc.0 
    L_0043: ldstr "  FROM table1 t1"
    L_0048: call string [mscorlib]System.String::Concat(string, string)
    L_004d: stloc.0 
    L_004e: ldloc.0 
    L_004f: ldstr "  JOIN table2 t2"
    L_0054: call string [mscorlib]System.String::Concat(string, string)
    L_0059: stloc.0 
    L_005a: ldloc.0 
    L_005b: ldstr "    ON t1.column1 = t2.column1"
    L_0060: call string [mscorlib]System.String::Concat(string, string)
    L_0065: stloc.0 
    L_0066: ldloc.0 
    L_0067: ret 
}

Method concatenating 9 strings with String.Concat and a string array.
Uses 9 more String instances than our base.
(1 due to Concat and 8 extra strings)


.method public hidebysig instance string GetConcatedString() cil managed
{
    .maxstack 3
    .locals init (
        [0] string[] pieces,
        [1] string[] CS$0$0000)
    L_0000: ldc.i4.s 9
    L_0002: newarr string
    L_0007: stloc.1 
    L_0008: ldloc.1 
    L_0009: ldc.i4.0 
    L_000a: ldstr "SELECT column1,"
    L_000f: stelem.ref 
    L_0010: ldloc.1 
    L_0011: ldc.i4.1 
    L_0012: ldstr "       column2,"
    L_0017: stelem.ref 
    L_0018: ldloc.1 
    L_0019: ldc.i4.2 
    L_001a: ldstr "       column3,"
    L_001f: stelem.ref 
    L_0020: ldloc.1 
    L_0021: ldc.i4.3 
    L_0022: ldstr "       column4,"
    L_0027: stelem.ref 
    L_0028: ldloc.1 
    L_0029: ldc.i4.4 
    L_002a: ldstr "       column5,"
    L_002f: stelem.ref 
    L_0030: ldloc.1 
    L_0031: ldc.i4.5 
    L_0032: ldstr "       column6,"
    L_0037: stelem.ref 
    L_0038: ldloc.1 
    L_0039: ldc.i4.6 
    L_003a: ldstr "  FROM table1 t1"
    L_003f: stelem.ref 
    L_0040: ldloc.1 
    L_0041: ldc.i4.7 
    L_0042: ldstr "  JOIN table2 t2"
    L_0047: stelem.ref 
    L_0048: ldloc.1 
    L_0049: ldc.i4.8 
    L_004a: ldstr "    ON t1.column1 = t2.column1"
    L_004f: stelem.ref 
    L_0050: ldloc.1 
    L_0051: stloc.0 
    L_0052: ldloc.0 
    L_0053: call string [mscorlib]System.String::Concat(string[])
    L_0058: ret 
}

Method concatenating 9 strings with StringBuilder.Append()
Uses 13 more String instances than our base.
(1 from StringBuilder.ToString(), 4 from StringBuilder increasing in size and 8 extra strings)


.method public hidebysig instance string GetBuildString() cil managed
{
    .maxstack 2
    .locals init (
        [0] class [mscorlib]System.Text.StringBuilder builder)
    L_0000: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor()
    L_0005: stloc.0 
    L_0006: ldloc.0 
    L_0007: ldstr "SELECT column1,"
    L_000c: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
    L_0011: pop 
    L_0012: ldloc.0 
    L_0013: ldstr "       column2,"
    L_0018: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
    L_001d: pop 
    L_001e: ldloc.0 
    L_001f: ldstr "       column3,"
    L_0024: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
    L_0029: pop 
    L_002a: ldloc.0 
    L_002b: ldstr "       column4,"
    L_0030: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
    L_0035: pop 
    L_0036: ldloc.0 
    L_0037: ldstr "       column5,"
    L_003c: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
    L_0041: pop 
    L_0042: ldloc.0 
    L_0043: ldstr "       column6,"
    L_0048: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
    L_004d: pop 
    L_004e: ldloc.0 
    L_004f: ldstr "  FROM table1 t1"
    L_0054: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
    L_0059: pop 
    L_005a: ldloc.0 
    L_005b: ldstr "  JOIN table2 t2"
    L_0060: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
    L_0065: pop 
    L_0066: ldloc.0 
    L_0067: ldstr "    ON t1.column1 = t2.column1"
    L_006c: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
    L_0071: pop 
    L_0072: ldloc.0 
    L_0073: callvirt instance string [mscorlib]System.Object::ToString()
    L_0078: ret 
}

Method concatenating 9 strings with StringBuilder.AppendFormat()
Uses 17 more String instances than our base.
(Excluding from tests do to Object[] and Char[] creation to parse the format and arguments)


.method public hidebysig instance string GetBuildFormatString() cil managed
{
    .maxstack 3
    .locals init (
        [0] class [mscorlib]System.Text.StringBuilder builder)
    L_0000: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor()
    L_0005: stloc.0 
    L_0006: ldloc.0 
    L_0007: ldstr "SELECT {0},"
    L_000c: ldstr "column1"
    L_0011: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::AppendFormat(string, object)
    L_0016: pop 
    L_0017: ldloc.0 
    L_0018: ldstr "       {0},"
    L_001d: ldstr "column2"
    L_0022: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::AppendFormat(string, object)
    L_0027: pop 
    L_0028: ldloc.0 
    L_0029: ldstr "       {0},"
    L_002e: ldstr "column3"
    L_0033: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::AppendFormat(string, object)
    L_0038: pop 
    L_0039: ldloc.0 
    L_003a: ldstr "       {0},"
    L_003f: ldstr "column4"
    L_0044: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::AppendFormat(string, object)
    L_0049: pop 
    L_004a: ldloc.0 
    L_004b: ldstr "       {0},"
    L_0050: ldstr "column5"
    L_0055: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::AppendFormat(string, object)
    L_005a: pop 
    L_005b: ldloc.0 
    L_005c: ldstr "       {0},"
    L_0061: ldstr "column6"
    L_0066: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::AppendFormat(string, object)
    L_006b: pop 
    L_006c: ldloc.0 
    L_006d: ldstr "  FROM {0} t1"
    L_0072: ldstr "table1"
    L_0077: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::AppendFormat(string, object)
    L_007c: pop 
    L_007d: ldloc.0 
    L_007e: ldstr "  JOIN {0} t2"
    L_0083: ldstr "table2"
    L_0088: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::AppendFormat(string, object)
    L_008d: pop 
    L_008e: ldloc.0 
    L_008f: ldstr "    ON t1.column1 = t2.column1"
    L_0094: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
    L_0099: pop 
    L_009a: ldloc.0 
    L_009b: callvirt instance string [mscorlib]System.Object::ToString()
    L_00a0: ret 
}




------------------------------------------------------------------------------------------

Same as above, but executed in a small loop of 50 calls.

Method returning a string. No allocation seems to take place (maybe 1).
Identical amount of String instances. String Interning at work.


Method += concatenating 9 strings. Translates into 400 String.Concat calls.
Uses 408 more String instances than our base.
(400 due to Concat and 8 extra strings in code)


Method concatenating 9 strings with String.Concat and a string array.
Uses 58 more String instances than our base.
(50 due to Concat and 8 extra strings)


Method concatenating 9 strings with StringBuilder.Append()
Uses 258 more String instances than our base.
(50 from StringBuilder.ToString(), 200 from StringBuilder increasing in size and 8 extra strings)


Method concatenating 9 strings with StringBuilder.AppendFormat()
Uses 262 more String instances than our base.
(Excluding from tests do to Object[] and Char[] creation to parse the format and arguments)
(75kB usage versus 54kB usage with a normal StringBuilder demonstrates why this is excluded)