Dot Net Perls

StringBuilder Secrets in C#

by Sam Allen - Updated June 13, 2009

Problem. You need to append strings together, yielding a longer string, using the C# programming language. Measure performance and avoid string append methods that can cause severe performance problems. Solution. Here we see how you can start using StringBuilder by including the System.Text namespace in your file. You usually use StringBuilder in a loop, and the examples here show more usages.

       String type: Has immutable buffers.
                    Cannot be changed.
                    Each operation returns a new string.
                    Copies can increase memory pressure.

StringBuilder type: Has mutable buffers.
                    Can be changed without copying.
                    Usually used in loops.

1. Using StringBuilder

First, we see some of the essential methods on the StringBuilder type in the base class library. The methods shown here will allow you to use it effectively in many programs, appending strings and lines. This example does not show a loop, and it is not ideal as a program, but it is for demonstration purposes.

=== Program that uses StringBuilder (C#) ===

using System;
using System.Text;
using System.Diagnostics;

class Program
{
    static void Main()
    {
        // 1.
        // Declare a new StringBuilder.
        StringBuilder builder = new StringBuilder();

        // 2.
        builder.Append("The list starts here:");

        // 3.
        builder.AppendLine();

        // 4.
        builder.Append("1 cat").AppendLine();

        // 5.
        // Get a reference to the StringBuilder's buffer content.
        string innerString = builder.ToString();

        // Display with Debug.
        Debug.WriteLine(innerString);
    }
}

=== Output of the program ===

The list starts here:
1 cat

Description of the new keyword. It uses the 'new' keyword for StringBuilder. Use the new keyword to make your StringBuilder. This is different from regular strings. StringBuilder has many overloaded constructors.

Description of appending. It calls the instance Append() method. This method adds the contents of its arguments to the buffer in the StringBuilder. Every argument to StringBuilder will automatically have its ToString method called.

Using AppendLine method. It calls AppendLine, which does the exact same thing as Append(), except with a newline on the end. Next, Append and AppendLine call themselves. This shows terse syntax with StringBuilder. Finally, ToString returns the buffer. You will almost always want ToString(). It will return the contents as a string.

2. Memory pressure

StringBuilder will make your appends go faster usually, but there's another benefit. In .NET, there is a concept of "memory pressure", meaning that the more temporary objects created by your app, the more often garbage collection runs. StringBuilder creates fewer temporary objects and adds less memory pressure.

3. Replacing and inserting

Here we see how you can use StringBuilder to replace or insert characters in loops. First convert the string to a StringBuilder, and then call StringBuilder's methods to do these operations. This is faster because the StringBuilder type uses character arrays internally, not unchangeable strings. [C# Replace String Examples - dotnetperls.com]

=== Program that uses Replace (C#) ===

using System;
using System.Text;

class Program
{
    static void Main()
    {
        StringBuilder builder = new StringBuilder(
            "This is an example string that is an example.");
        builder.Replace("an", "the"); // Replaces 'an' with 'the'.
        Console.WriteLine(builder.ToString());
        Console.ReadLine();
    }
}

=== Output of the program ===

This is the example string that is the example.
Question

4. When shouldn't I use StringBuilder?

When you don't have a loop, generally you should avoid StringBuilder. There is lots of logic in StringBuilder that will be slow on very small operations. StringBuilder can make your code more cumbersome. Here we look at some potential problems with using StringBuilder in problems.

When to consider char arrays. Character arrays, specified as char[], are vastly simpler, but your code must be more precise. If you know a maximum or absolute size of your output string, and your requirements are simple, use char arrays.

Consider this common mistake. Many developers make a very specific StringBuilder mistake that reduces speed by about 40%. Don't use the + operator on strings within a StringBuilder. It will simply draw in the slowness of strings, resulting in many temporary assignments and copies to the managed heap. [C# StringBuilder Mistake - dotnetperls.com]

What about AppendFormat? AppendFormat draws in regular strings and substitutions. Internally, many versions of AppendFormat in the .NET Framework are implemented with StringBuilder instances. It is usually faster to call Append repeatedly with all the required parts. However, the syntax of AppendFormat can be clearer to read and maintain in some programs. You can see an example of the AppendFormat method in the section "Using AppendFormat."

5. Looping

Here we see how you can use StringBuilder in a simple loop. As I have noted, almost always your StringBuilder will be used in a loop. This can be a foreach, for, or while loop. Here's another example of StringBuilder, but in a foreach loop.

=== Program that uses foreach (C#) ===

using System;
using System.Text;

class Program
{
    static void Main()
    {
        string[] items = { "Cat", "Dog", "Celebrity" };

        StringBuilder builder2 = new StringBuilder(
            "These items are required:").AppendLine();

        foreach (string item in items)
        {
            builder2.Append(item).AppendLine();
        }
        Console.WriteLine(builder2.ToString());
        Console.ReadLine();
    }
}

=== Output of the program ===

These items are required:
Cat
Dog
Celebrity

6. Using AppendFormat

Here we look at the useful AppendFormat method on the StringBuilder type. Format strings in C# are a powerful of simplifying string changes for display. The AppendFormat method uses the same syntax. There are many advanced rules for string formatting, which are out of the scope of this document.

=== Program that uses AppendFormat (C#) ===

using System;
using System.Text;

class Program
{
    static int[] _v = new int[]
    {
        1,
        4,
        6
    };

    static void Main()
    {
        StringBuilder b = new StringBuilder();
        foreach (int v in _v)
        {
            b.AppendFormat("int: {0:0.0}" + Environment.NewLine,
                v);
        }
        Console.WriteLine(b.ToString());
    }
}

=== Output of the program ===

int: 1.0
int: 4.0
int: 6.0

7. Benchmarking StringBuilder

This site has a separate article on benchmarks of StringBuilder versus string here. Further on in the article you are reading, you can see a way of using StringBuilder more effectively as a parameter. The key to good performance here is to avoid string copies, thereby reducing allocations on the managed heap. The benchmarks here are based on the .NET Framework 3.5 SP1. [C# StringBuilder Performance - dotnetperls.com]

8. Testing for equality

You will find that StringBuilder defines an instance method Equals that can be used to compare the capacities and contents of two StringBuilders. It is important to use this method because you can avoid lots of error-prone code that you write yourself. There are some subtleties to Equals, however, which are looked into on this site. [C# StringBuilder Equals Method - dotnetperls.com]

9. Using StringBuilder parameter

Here we can see how you can use the StringBuilder type as a method parameter. This is a significant optimization because it will avoid converting back and forth to strings. The example shows StringBuilder parameters and reusing the same StringBuilder. Strings alone would be hugely slower in many cases.

=== Example 1: many StringBuilders created (slow) ===

using System;
using System.Text;

class Program
{
    static string[] _items = new string[]
    {
        "cat",
        "dog",
        "giraffe"
    };

    /// <summary>
    /// Append to a new StringBuilder and return it as a string.
    /// </summary>
    static string A1()
    {
        StringBuilder b = new StringBuilder();
        foreach (string item in _items)
        {
            b.AppendLine(item);
        }
        return b.ToString();
    }

    static void Main()
    {
        // Called in loop.
        A1();
    }
}

=== Example 2: one StringBuilder used as parameter ===

using System;
using System.Text;

class Program
{
    static string[] _items = new string[]
    {
        "cat",
        "dog",
        "giraffe"
    };

    /// <summary>
    /// Append to the StringBuilder param, void method.
    /// </summary>
    static void A2(StringBuilder b)
    {
        foreach (string item in _items)
        {
            b.AppendLine(item);
        }
    }

    static void Main()
    {
        // Called in loop.
        StringBuilder b = new StringBuilder();
        A2(b);
    }
}

=== Important differences ===

Version 1: Many StringBuilders created
           Increased memory pressure

Version 2: One StringBuilder created
           Less memory pressure
           Same result as before

=== Benchmark results (many iterations of methods) ===

Version 1: 5039 ms
Version 2: 3073 ms

10. Understanding 'immutable' strings

The word 'immutable' indicates that the data being pointed at is not changeable. To see an example of an immutable object in the C# language, try to assign to a character in a string. This causes a compile-time error, because the string type does not define a set accessor. However, character arrays can be changed. Internally, StringBuilder uses "mutable" char arrays for its buffer. [C# string Append, Adding Strings Together - dotnetperls.com]

11. Avoiding ArgumentOutOfRangeException

You may be getting this exception if you are putting too much data in your StringBuilder. The maximum number of characters in a StringBuilder is equal to Int32.MaxValue. The author suggests you check for infinite loops or other serious problems. [C# int.Max and Min Constants - dotnetperls.com]

12. Members

Here we look at more members on the StringBuilder type in the .NET Framework base class library. This document only shows some of the StringBuilder members. The Replace, Insert and Remove methods are very important, even though they are less common than Append. [StringBuilder Members - MSDN]

Method: Append
Usage:  MSDN description:
        "Appends the string representation of a specified object
        to the end of this instance."

Method: AppendFormat
Usage:  Appends string using the formatting syntax available in
        string.Format.
        This can improve code clarity.

Method: EnsureCapacity
Usage:  This rarely is useful for changing the capacity.
        This is an optimization you can use.

Method: Insert
Usage:  Very similar to Replace.
        Used to add characters at an index.

Method: Remove
Usage:  Essentially the same as the Remove method on string.
        Avoids character array copying.

Method: Replace
Usage:  Replaces one string of characters with another.
        See the article on the Replace method.

13. Summary

Here we saw ways you can effectively use StringBuilder in your application. StringBuilder can improve the performance of your program, even if you misuse it. However, by using it optimally, results are much better. Use StringBuilder as a parameter instead of calling ToString frequently. The article doesn't show many genuine secrets, but the tips are valuable nonetheless.

Dot Net Perls
StringBuilder | StringBuilder Append Syntax | StringBuilder Data Types | StringBuilder Mistake | StringBuilder Performance Test | StringWriter Use
C# | Reflection Field Example | Validate Characters in String | Main Args Examples | Enum String Method
© 2009 Sam Allen. All rights reserved.