Between work and stuff going on in my personal life, there hasn't been a lot of spare time for blogging and developer community things over the last few weeks. Hopefully, that will be changing in the near future. In the meantime, I wanted to squeeze in a quick blog post about something that came up today regarding StringBuilder.
It was pointed out someone on my team was concatenating several strings to build a query in a few methods. It is arguable whether these particular queries should be embedded in the source code, but that isn't what this post is about. It was requested that the developer modify the source code to use a StringBuilder rather than concatenating the strings. Generally speaking, I would agree with this approach as the StringBuilder is more efficient when concatenating several strings, but this wasn't one of those cases.
Consider the following two methods:
public static string LiteralConcatenationExample()
{
return "SELECT T1.Field1, " +
"T1.Field2, " +
"T1.Field3, " +
"T2.Field4 " +
"FROM Table1 T1 " +
"INNER JOIN Table2 T2 ON T1.Field1 = T2.Field1";
}
public static string StringBuilderWithLiteralsExample()
{
StringBuilder sb = new StringBuilder();
sb.Append("SELECT T1.Field1, ");
sb.Append("T1.Field2, ");
sb.Append("T1.Field3, ");
sb.Append("T2.Field4 ");
sb.Append("FROM Table1 T1 ");
sb.Append("INNER JOIN Table2 T2 ON T1.Field1 = T2.Field1");
return sb.ToString();
}
Which method do you think would be more efficient?
If you chose the second method, you should take a closer look at the generated IL:
.method public hidebysig static string LiteralConcatenationExample() cil managed
{
// Code size 11 (0xb)
.maxstack 1
.locals init ([0] string CS$1$0000)
IL_0000: nop
IL_0001: ldstr "SELECT T1.Field1, T1.Field2, T1.Field3, T2.Field4 "
+ "FROM Table1 T1 INNER JOIN Table2 T2 ON T1.Field1 = T2.Field1"
IL_0006: stloc.0
IL_0007: br.s IL_0009
IL_0009: ldloc.0
IL_000a: ret
} // end of method Program::LiteralConcatenationExample.method public hidebysig static string StringBuilderWithLiteralsExample() cil managed
{
// Code size 90 (0x5a)
.maxstack 2
.locals init ([0] class [mscorlib]System.Text.StringBuilder sb,
[1] string CS$1$0000)
IL_0000: nop
IL_0001: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor()
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: ldstr "SELECT T1.Field1, "
IL_000d: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
IL_0012: pop
IL_0013: ldloc.0
IL_0014: ldstr "T1.Field2, "
IL_0019: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
IL_001e: pop
IL_001f: ldloc.0
IL_0020: ldstr "T1.Field3, "
IL_0025: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
IL_002a: pop
IL_002b: ldloc.0
IL_002c: ldstr "T2.Field4 "
IL_0031: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
IL_0036: pop
IL_0037: ldloc.0
IL_0038: ldstr "FROM Table1 T1 "
IL_003d: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
IL_0042: pop
IL_0043: ldloc.0
IL_0044: ldstr "INNER JOIN Table2 T2 ON T1.Field1 = T2.Field1"
IL_0049: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
IL_004e: pop
IL_004f: ldloc.0
IL_0050: callvirt instance string [mscorlib]System.Object::ToString()
IL_0055: stloc.1
IL_0056: br.s IL_0058
IL_0058: ldloc.1
IL_0059: ret
} // end of method Program::StringBuilderWithLiteralsExample
As you can see, the string concatenation is considerable more efficient. This may be a bit confusing to some people as you have undoubtedly had it hammered into your head that a StringBuilder should be used when concatenating strings. Well, I would agree when you are dealing with dynamic strings that are built using parameters. However, literal strings are a different story.
Since this scenario is using literal strings, the compiler can optimize the values into one large string that can be loaded with a single load string operation (ldstr). If the code is modified to use a StringBuilder for each line of the query, it actually increases the number of operations by four for each line of the query.
In fairness, most situations will probably be dealing with dynamic strings, which would change this scenario back to favor the usage of a StringBuilder. However, you should keep in mind that a StringBuilder doesn't automagically give you a more efficient solution in every situation, especially if you are only concatenating a few strings.