Am I undermining the efficiency of StringBuilder?
I've started using StringBuilder
in preference to straight concatenation, but it seems like it's missing a crucial method. So, I implemented it myself, as an extension:
public void Append(this StringBuilder stringBuilder, params string[] args)
{
foreach (string arg in args)
stringBuilder.Append(arg);
}
This turns the following mess:
StringBuilder sb = new StringBuilder();
...
sb.Append(SettingNode);
sb.Append(KeyAttribute);
sb.Append(setting.Name);
Into this:
sb.Append(SettingNode, KeyAttribute, setting.Name);
I could use sb.AppendFormat("{0}{1}{2}",...
, but this seems even less preferred, and still harder to read. Is my extension a good method, or does it somehow undermine the benefits of StringBuilder
? I'm not trying to prematurely optimize anything, as my method is more about readability than speed, but I'd also like to know I'm not shooting myself in the foot.
I see no problem with your extension. If it works for you it's all good.
I myself prefere:
sb.Append(SettingNode)
.Append(KeyAttribute)
.Append(setting.Name);
Questions like this can always be answered with a simple test case.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
namespace SBTest
{
class Program
{
private const int ITERATIONS = 1000000;
private static void Main(string[] args)
{
Test1();
Test2();
Test3();
}
private static void Test1()
{
var sw = Stopwatch.StartNew();
var sb = new StringBuilder();
for (var i = 0; i < ITERATIONS; i++)
{
sb.Append("TEST" + i.ToString("00000"),
"TEST" + (i + 1).ToString("00000"),
"TEST" + (i + 2).ToString("00000"));
}
sw.Stop();
Console.WriteLine("Testing Append() extension method...");
Console.WriteLine("--------------------------------------------");
Console.WriteLine("Test 1 iterations: {0:n0}", ITERATIONS);
Console.WriteLine("Test 1 milliseconds: {0:n0}", sw.ElapsedMilliseconds);
Console.WriteLine("Test 1 output length: {0:n0}", sb.Length);
Console.WriteLine("");
}
private static void Test2()
{
var sw = Stopwatch.StartNew();
var sb = new StringBuilder();
for (var i = 0; i < ITERATIONS; i++)
{
sb.Append("TEST" + i.ToString("00000"));
sb.Append("TEST" + (i+1).ToString("00000"));
sb.Append("TEST" + (i+2).ToString("00000"));
}
sw.Stop();
Console.WriteLine("Testing multiple calls to Append() built-in method...");
Console.WriteLine("--------------------------------------------");
Console.WriteLine("Test 2 iterations: {0:n0}", ITERATIONS);
Console.WriteLine("Test 2 milliseconds: {0:n0}", sw.ElapsedMilliseconds);
Console.WriteLine("Test 2 output length: {0:n0}", sb.Length);
Console.WriteLine("");
}
private static void Test3()
{
var sw = Stopwatch.StartNew();
var sb = new StringBuilder();
for (var i = 0; i < ITERATIONS; i++)
{
sb.AppendFormat("{0}{1}{2}",
"TEST" + i.ToString("00000"),
"TEST" + (i + 1).ToString("00000"),
"TEST" + (i + 2).ToString("00000"));
}
sw.Stop();
Console.WriteLine("Testing AppendFormat() built-in method...");
Console.WriteLine("--------------------------------------------");
Console.WriteLine("Test 3 iterations: {0:n0}", ITERATIONS);
Console.WriteLine("Test 3 milliseconds: {0:n0}", sw.ElapsedMilliseconds);
Console.WriteLine("Test 3 output length: {0:n0}", sb.Length);
Console.WriteLine("");
}
}
public static class SBExtentions
{
public static void Append(this StringBuilder sb, params string[] args)
{
foreach (var arg in args)
sb.Append(arg);
}
}
}
On my PC, the output is:
Testing Append() extension method...
--------------------------------------------
Test 1 iterations: 1,000,000
Test 1 milliseconds: 1,080
Test 1 output length: 29,700,006
Testing multiple calls to Append() built-in method...
--------------------------------------------
Test 2 iterations: 1,000,000
Test 2 milliseconds: 1,001
Test 2 output length: 29,700,006
Testing AppendFormat() built-in method...
--------------------------------------------
Test 3 iterations: 1,000,000
Test 3 milliseconds: 1,124
Test 3 output length: 29,700,006
So your extension method is only slightly slower than the Append() method and is slightly faster than the AppendFormat() method, but in all 3 cases, the difference is entirely too trivial to worry about. Thus, if your extension method enhances the readability of your code, use it!
It's a little bit of overhead creating the extra array, but I doubt that it's a lot. You should measure
If it turns out that the overhead of creating string arrays is significant, you can mitigate it by having several overloads - one for two parameters, one for three, one for four etc... so that only when you get to a higher number of parameters (e.g. six or seven) will it need to create the array. The overloads would be like this:
public void Append(this builder, string item1, string item2)
{
builder.Append(item1);
builder.Append(item2);
}
public void Append(this builder, string item1, string item2, string item3)
{
builder.Append(item1);
builder.Append(item2);
builder.Append(item3);
}
public void Append(this builder, string item1, string item2,
string item3, string item4)
{
builder.Append(item1);
builder.Append(item2);
builder.Append(item3);
builder.Append(item4);
}
// etc
And then one final overload using params
, e.g.
public void Append(this builder, string item1, string item2,
string item3, string item4, params string[] otherItems)
{
builder.Append(item1);
builder.Append(item2);
builder.Append(item3);
builder.Append(item4);
foreach (string item in otherItems)
{
builder.Append(item);
}
}
I'd certainly expect these (or just your original extension method) to be faster than using AppendFormat
- which needs to parse the format string, after all.
Note that I didn't make these overloads call each other pseudo-recursively - I suspect they'd be inlined, but if they weren't the overhead of setting up a new stack frame etc could end up being significant. (We're assuming the overhead of the array is significant, if we've got this far.)