161
votes

Suppose I have a stringbuilder in C# that does this:

StringBuilder sb = new StringBuilder();
string cat = "cat";
sb.Append("the ").Append(cat).(" in the hat");
string s = sb.ToString();

would that be as efficient or any more efficient as having:

string cat = "cat";
string s = String.Format("The {0} in the hat", cat);

If so, why?

EDIT

After some interesting answers, I realised I probably should have been a little clearer in what I was asking. I wasn't so much asking for which was quicker at concatenating a string, but which is quicker at injecting one string into another.

In both cases above I want to inject one or more strings into the middle of a predefined template string.

Sorry for the confusion

12
Please leave these open to allow to future improvements.Mark Biek
In a special-case scenario, the quickest is neither of these: if the part to be replaced is equal in size to the new part, you can change the string in-place. Unfortunately, this requires reflection or unsafe code and deliberately violates the immutability of the string. Not a good practice, but if speed is an issue... :)Abel
in the example given above string s = "The "+cat+" in the hat"; might be the fastest unless it is used in a loop, in which case fastest will be with a StringBuilder initialised outside the loop.Surya Pratap

12 Answers

148
votes

NOTE: This answer was written when .NET 2.0 was the current version. This may no longer apply to later versions.

String.Format uses a StringBuilder internally:

public static string Format(IFormatProvider provider, string format, params object[] args)
{
    if ((format == null) || (args == null))
    {
        throw new ArgumentNullException((format == null) ? "format" : "args");
    }

    StringBuilder builder = new StringBuilder(format.Length + (args.Length * 8));
    builder.AppendFormat(provider, format, args);
    return builder.ToString();
}

The above code is a snippet from mscorlib, so the question becomes "is StringBuilder.Append() faster than StringBuilder.AppendFormat()"?

Without benchmarking I'd probably say that the code sample above would run more quickly using .Append(). But it's a guess, try benchmarking and/or profiling the two to get a proper comparison.

This chap, Jerry Dixon, did some benchmarking:

http://jdixon.dotnetdevelopersjournal.com/string_concatenation_stringbuilder_and_stringformat.htm

Updated:

Sadly the link above has since died. However there's still a copy on the Way Back Machine:

http://web.archive.org/web/20090417100252/http://jdixon.dotnetdevelopersjournal.com/string_concatenation_stringbuilder_and_stringformat.htm

At the end of the day it depends whether your string formatting is going to be called repetitively, i.e. you're doing some serious text processing over 100's of megabytes of text, or whether it's being called when a user clicks a button now and again. Unless you're doing some huge batch processing job I'd stick with String.Format, it aids code readability. If you suspect a perf bottleneck then stick a profiler on your code and see where it really is.

45
votes

From the MSDN documentation:

The performance of a concatenation operation for a String or StringBuilder object depends on how often a memory allocation occurs. A String concatenation operation always allocates memory, whereas a StringBuilder concatenation operation only allocates memory if the StringBuilder object buffer is too small to accommodate the new data. Consequently, the String class is preferable for a concatenation operation if a fixed number of String objects are concatenated. In that case, the individual concatenation operations might even be combined into a single operation by the compiler. A StringBuilder object is preferable for a concatenation operation if an arbitrary number of strings are concatenated; for example, if a loop concatenates a random number of strings of user input.

12
votes

I ran some quick performance benchmarks, and for 100,000 operations averaged over 10 runs, the first method (String Builder) takes almost half the time of the second (String Format).

So, if this is infrequent, it doesn't matter. But if it is a common operation, then you may want to use the first method.

10
votes

I would expect String.Format to be slower - it has to parse the string and then concatenate it.

Couple of notes:

  • Format is the way to go for user-visible strings in professional applications; this avoids localization bugs
  • If you know the length of the resultant string beforehand, use the StringBuilder(Int32) constructor to predefine the capacity
8
votes

I think in most cases like this clarity, and not efficiency, should be your biggest concern. Unless you're crushing together tons of strings, or building something for a lower powered mobile device, this probably won't make much of a dent in your run speed.

I've found that, in cases where I'm building strings in a fairly linear fashion, either doing straight concatenations or using StringBuilder is your best option. I suggest this in cases where the majority of the string that you're building is dynamic. Since very little of the text is static, the most important thing is that it's clear where each piece of dynamic text is being put in case it needs updated in the future.

On the other hand, if you're talking about a big chunk of static text with two or three variables in it, even if it's a little less efficient, I think the clarity you gain from string.Format makes it worth it. I used this earlier this week when having to place one bit of dynamic text in the center of a 4 page document. It'll be easier to update that big chunk of text if its in one piece than having to update three pieces that you concatenate together.

8
votes

If only because string.Format doesn't exactly do what you might think, here is a rerun of the tests 6 years later on Net45.

Concat is still fastest but really it's less than 30% difference. StringBuilder and Format differ by barely 5-10%. I got variations of 20% running the tests a few times.

Milliseconds, a million iterations:

  • Concatenation: 367
  • New stringBuilder for each key: 452
  • Cached StringBuilder: 419
  • string.Format: 475

The lesson I take away is that the performance difference is trivial and so it shouldn't stop you writing the simplest readable code you can. Which for my money is often but not always a + b + c.

const int iterations=1000000;
var keyprefix= this.GetType().FullName;
var maxkeylength=keyprefix + 1 + 1+ Math.Log10(iterations);
Console.WriteLine("KeyPrefix \"{0}\", Max Key Length {1}",keyprefix, maxkeylength);

var concatkeys= new string[iterations];
var stringbuilderkeys= new string[iterations];
var cachedsbkeys= new string[iterations];
var formatkeys= new string[iterations];

var stopwatch= new System.Diagnostics.Stopwatch();
Console.WriteLine("Concatenation:");
stopwatch.Start();

for(int i=0; i<iterations; i++){
    var key1= keyprefix+":" + i.ToString();
    concatkeys[i]=key1;
}

Console.WriteLine(stopwatch.ElapsedMilliseconds);

Console.WriteLine("New stringBuilder for each key:");
stopwatch.Restart();

for(int i=0; i<iterations; i++){
    var key2= new StringBuilder(keyprefix).Append(":").Append(i.ToString()).ToString();
    stringbuilderkeys[i]= key2;
}

Console.WriteLine(stopwatch.ElapsedMilliseconds);

Console.WriteLine("Cached StringBuilder:");
var cachedSB= new StringBuilder(maxkeylength);
stopwatch.Restart();

for(int i=0; i<iterations; i++){
    var key2b= cachedSB.Clear().Append(keyprefix).Append(":").Append(i.ToString()).ToString();
    cachedsbkeys[i]= key2b;
}

Console.WriteLine(stopwatch.ElapsedMilliseconds);

Console.WriteLine("string.Format");
stopwatch.Restart();

for(int i=0; i<iterations; i++){
    var key3= string.Format("{0}:{1}", keyprefix,i.ToString());
    formatkeys[i]= key3;
}

Console.WriteLine(stopwatch.ElapsedMilliseconds);

var referToTheComputedValuesSoCompilerCantOptimiseTheLoopsAway= concatkeys.Union(stringbuilderkeys).Union(cachedsbkeys).Union(formatkeys).LastOrDefault(x=>x[1]=='-');
Console.WriteLine(referToTheComputedValuesSoCompilerCantOptimiseTheLoopsAway);
6
votes

String.Format uses StringBuilder internally, so logically that leads to the idea that it would be a little less performant due to more overhead. However, a simple string concatenation is the fastest method of injecting one string between two others, by a significant degree. This evidence was demonstrated by Rico Mariani in his very first Performance Quiz, years ago. Simple fact is that concatenations, when the number of string parts is known (without limitation — you could concatenate a thousand parts, as long as you know it's always 1000 parts), are always faster than StringBuilder or String.Format. They can be performed with a single memory allocation and a series of memory copies. Here is the proof.

And here is the actual code for some String.Concat methods, which ultimately call FillStringChecked, which uses pointers to copy memory (extracted via Reflector):

public static string Concat(params string[] values)
{
    int totalLength = 0;

    if (values == null)
    {
        throw new ArgumentNullException("values");
    }

    string[] strArray = new string[values.Length];

    for (int i = 0; i < values.Length; i++)
    {
        string str = values[i];
        strArray[i] = (str == null) ? Empty : str;
        totalLength += strArray[i].Length;

        if (totalLength < 0)
        {
            throw new OutOfMemoryException();
        }
    }

    return ConcatArray(strArray, totalLength);
}

public static string Concat(string str0, string str1, string str2, string str3)
{
    if (((str0 == null) && (str1 == null)) && ((str2 == null) && (str3 == null)))
    {
        return Empty;
    }

    if (str0 == null)
    {
        str0 = Empty;
    }

    if (str1 == null)
    {
        str1 = Empty;
    }

    if (str2 == null)
    {
        str2 = Empty;
    }

    if (str3 == null)
    {
        str3 = Empty;
    }

    int length = ((str0.Length + str1.Length) + str2.Length) + str3.Length;
    string dest = FastAllocateString(length);
    FillStringChecked(dest, 0, str0);
    FillStringChecked(dest, str0.Length, str1);
    FillStringChecked(dest, str0.Length + str1.Length, str2);
    FillStringChecked(dest, (str0.Length + str1.Length) + str2.Length, str3);
    return dest;
}

private static string ConcatArray(string[] values, int totalLength)
{
    string dest = FastAllocateString(totalLength);
    int destPos = 0;

    for (int i = 0; i < values.Length; i++)
    {
        FillStringChecked(dest, destPos, values[i]);
        destPos += values[i].Length;
    }

    return dest;
}

private static unsafe void FillStringChecked(string dest, int destPos, string src)
{
    int length = src.Length;

    if (length > (dest.Length - destPos))
    {
        throw new IndexOutOfRangeException();
    }

    fixed (char* chRef = &dest.m_firstChar)
    {
        fixed (char* chRef2 = &src.m_firstChar)
        {
            wstrcpy(chRef + destPos, chRef2, length);
        }
    }
}

So, then:

string what = "cat";
string inthehat = "The " + what + " in the hat!";

Enjoy!

3
votes

Oh also, the fastest would be:

string cat = "cat";
string s = "The " + cat + " in the hat";
0
votes

It really depends. For small strings with few concatenations, it's actually faster just to append the strings.

String s = "String A" + "String B";

But for larger string (very very large strings), it's then more efficient to use StringBuilder.

0
votes

In both cases above I want to inject one or more strings into the middle of a predefined template string.

In which case, I would suggest String.Format is the quickest because it is design for that exact purpose.

0
votes

It really depends on your usage pattern.
A detailed benchmark between string.Join, string,Concat and string.Format can be found here: String.Format Isn't Suitable for Intensive Logging

-1
votes

I would suggest not, since String.Format was not designed for concatenation, it was design for formatting the output of various inputs such as a date.

String s = String.Format("Today is {0:dd-MMM-yyyy}.", DateTime.Today);