StringBuilder
A C# string
can be built one piece at a time, but for strings, each append causes a string
copy. With StringBuilder
we eliminate this copy.
Unlike a string
, a StringBuilder
can be changed. With it, an algorithm that modifies characters in a loop runs fast—many string
copies are avoided.
This program uses StringBuilder
to build up a buffer of characters. We call Append()
to add more data to our StringBuilder
.
StringBuilder
class
instance (an object) by using the new-operator.Append()
. This method can be called directly on its own result, in the same statement.Append()
, and other methods like AppendFormat
, return the same StringBuilder
.using System; using System.Text; // Part 1: create new StringBuilder and loop. StringBuilder builder = new StringBuilder(); for (int i = 0; i < 3; i++) { // Part 2: append to StringBuilder. builder.Append(i).Append(" "); } Console.WriteLine(builder);0 1 2
AppendFormat
With this method, we add text to a StringBuilder
based on a pattern. We can use substitution markers to fill fields in this pattern.
Append
repeatedly with all the required parts. But the syntax of AppendFormat
may be clearer.using System; using System.Text; var builder = new StringBuilder(); // Append a format string directly. builder.AppendFormat("R: {0} ({1}).", "ABC", 1); Console.WriteLine(builder);R: ABC (1).
AppendLine
, ToString
To continue, we use other essential methods on StringBuilder
. Methods like Append()
, and ToString()
, are used in most C# programs that create StringBuilders
.
StringBuilder
. It begins its existence empty, with no buffered characters.Append
and AppendLine
. Arguments are converted to strings with ToString
. AppendLine
appends a newline.ToString
. This returns the buffer. We will usually want ToString
—it will return the contents as a string
.using System; using System.Text; // Part 1: declare a new StringBuilder. StringBuilder builder = new StringBuilder(); // Part 2: call Append and AppendLine. builder.Append("The list starts here:"); builder.AppendLine(); builder.Append("1 cat").AppendLine(); // Part 3: call ToString and display. string innerString = builder.ToString(); Console.WriteLine(innerString);The list starts here: 1 cat
Replace
This method replaces all instances of one string
with another. A string
is required, but we do not need to use a string
literal. The example exchanges "an" with "the."
Replace
method will replace all instances of the specified value. To replace one instance, we will need a custom method.using System; using System.Text; StringBuilder builder = new StringBuilder("This is an example string that is an example."); // Replace a word. builder.Replace("an", "the"); Console.WriteLine(builder);This is the example string that is the example.
Often we use StringBuilders
in loops. If many appends are needed, sometimes StringBuilder
is helpful in other contexts. Here is an example of StringBuilder
in a foreach
-loop.
foreach
, are effective when used with StringBuilder
.using System; using System.Text; 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);These items are required: Cat Dog Celebrity
StringBuilder
can be passed as an argument. This is a nice optimization: it avoids converting back and forth to strings.
StringBuilders
) is an effective way to improve program performance.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() { StringBuilder b = new StringBuilder(); A2(b); } }
It is possible to use the indexer to access or change certain characters. This syntax is the same as the syntax for accessing characters in a string
instance.
using System; using System.Text; StringBuilder builder = new StringBuilder(); builder.Append("cat"); // Write second letter. Console.WriteLine(builder[1]); // Change first letter. builder[0] = 'r'; Console.WriteLine(builder.ToString());a rat
Remove
This method removes a range of characters by index from the internal buffer. As with other StringBuilder
methods, this just rearranges the internal buffer.
using System; using System.Text; StringBuilder builder = new StringBuilder("Dot Net Perls"); // Remove based on index and count. builder.Remove(4, 3); Console.WriteLine(builder);Dot Perls
Append
substringWe can append a substring directly from another string
. No Substring
call is needed. We use the Append
method to do this.
char
after the space is the start of the substring we want.string
, the start index, and then the computed length (which continues until the end of the string
).Substring()
to create an intermediate string
first.using System; using System.Text; var builder = new StringBuilder(); string value = "bird frog"; // Get the index of the char after the space. int afterSpace = value.IndexOf(' ') + 1; // Append a substring, computing the length of the target range. builder.Append(value, afterSpace, value.Length - afterSpace); Console.WriteLine("APPEND SUBSTRING: {0}", builder);APPEND SUBSTRING: frog
ToString
This method is optimized—it will not copy data in certain situations. These optimizations are hard to duplicate in custom code.
ToString
with no arguments. This converts the entire StringBuilder
into a string
.ToString
method. The second argument is the count of chars, not the end index.ToString
method has some advanced optimizations to reduce copying. It should be used when a string
is required.using System; using System.Text; var builder = new StringBuilder("abcdef"); // Use ToString with no arguments. string result = builder.ToString(); Console.WriteLine("TOSTRING: {0}", result); // Use a start and length. string resultRange = builder.ToString(3, 3); Console.WriteLine("TOSTRING RANGE: {0}", resultRange);TOSTRING: abcdef TOSTRING RANGE: def
AppendJoin
We can invoke AppendJoin
on the StringBuilder
. This eliminates a loop: we can append many elements (joined together) in a single statement.
StringBuilder
and a string
array. We call AppendJoin
on the StringBuilder
—this is like calling string.Join
and Append
.AppendJoin
with an int
array. The ints are appended with inner separators.using System; using System.Text; // Part 1: use AppendJoin with string array. var builder = new StringBuilder(); string[] elements = { "bird", "frog", "dog" }; builder.AppendJoin(",", elements); Console.WriteLine(builder); // Part 2: use AppendJoin with int array. builder.Clear(); int[] values = { 10, 20, 30 }; builder.AppendJoin(".", values); Console.WriteLine(builder);bird,frog,dog 10.20.30
Trim
StringBuilder
has no Trim
, TrimStart
or TrimEnd
methods. But we can implement similar methods. Here we add a TrimEnd
method that removes a final character.
StringBuilder
for a matching char
. It then reduces Length
by 1 to erase it.char
will be removed—we could use a while
-loop to remove multiple matching chars.using System; using System.Text; class Program { static void TrimEnd(StringBuilder builder, char letter) { // ... If last char matches argument, reduce length by 1. if (builder[builder.Length - 1] == letter) { builder.Length -= 1; } } static void Main() { StringBuilder builder = new StringBuilder(); builder.Append("This has an end period."); Console.WriteLine(builder); TrimEnd(builder, '.'); Console.WriteLine(builder); } }This has an end period. This has an end period
To clear a StringBuilder
, it is sometimes best to allocate a new one. Other times, we can assign the Length
property to zero or use the Clear method.
using System; using System.Text; var builder = new StringBuilder(); for (int i = 0; i < 10; i++) { builder.Append(i); } Console.WriteLine("Before Clear(): {0}", builder.Length); builder.Clear(); Console.WriteLine("After Clear(): {0}", builder.Length);Before Clear(): 10 After Clear(): 0
string
concatSometimes we make a StringBuilder
mistake that reduces speed. We use the plus operator on strings within a StringBuilder
—this is bad.
string
to the StringBuilder
Append()
method.string
individually, avoiding a temporary string
creation.Append()
individually and never to create any temporary strings with the plus operator.using System; using System.Diagnostics; using System.Text; const int _max = 1000000; var builder1 = new StringBuilder(); var builder2 = new StringBuilder(); var tempString = 100.ToString(); // Version 1: concat strings then Append. var s1 = Stopwatch.StartNew(); for (int i = 0; i < _max; i++) { builder1.Append(tempString + ","); } s1.Stop(); // Version 2: append individual strings. var s2 = Stopwatch.StartNew(); for (int i = 0; i < _max; i++) { builder2.Append(tempString).Append(","); } s2.Stop(); Console.WriteLine(((double)(s1.Elapsed.TotalMilliseconds * 1000000) / _max).ToString("0.00 ns")); Console.WriteLine(((double)(s2.Elapsed.TotalMilliseconds * 1000000) / _max).ToString("0.00 ns"));38.92 ns Concat strings, then Append 15.42 ns Append twice
With the appropriate StringBuilder
Append
overload, we append a part of another string
. This eliminates extra string
copies.
StringBuilder
.Append
with 3 arguments. This is equivalent to the Substring
call but much faster.StringBuilder
Append
version that avoids a separate Substring
call is faster.using System; using System.Diagnostics; using System.Text; class Program { static void Method1(string input, StringBuilder buffer) { buffer.Clear(); string temp = input.Substring(3, 2); buffer.Append(temp); } static void Method2(string input, StringBuilder buffer) { buffer.Clear(); buffer.Append(input, 3, 2); } static void Main() { const int m = 100000000; var builder = new StringBuilder(); var s1 = Stopwatch.StartNew(); // Version 1: take Substring. for (int i = 0; i < m; i++) { Method1("perls", builder); } s1.Stop(); var s2 = Stopwatch.StartNew(); // Version 2: append range with Append. for (int i = 0; i < m; i++) { Method2("perls", builder); } s2.Stop(); Console.WriteLine(((double)(s1.Elapsed.TotalMilliseconds * 1000000) / m).ToString("0.00 ns")); Console.WriteLine(((double)(s2.Elapsed.TotalMilliseconds * 1000000) / m).ToString("0.00 ns")); } }33.47 ns Append string 25.14 ns Append range of string
CopyTo
With the CopyTo
method on StringBuilder
we can copy the StringBuilder
data to a char
array. But is this faster than using other methods?
CopyTo
on the StringBuilder
to copy a 100-char
buffer to a char
array.for
-loop to copy the data. Each element is individually assigned.CopyTo
on StringBuilder
is much faster and should be always preferred when many characters are being copied.using System; using System.Diagnostics; using System.Text; const int _max = 10000000; char[] array = new char[100]; var builder = new StringBuilder(new string('a', 100)); var s1 = Stopwatch.StartNew(); // Version 1: use CopyTo. for (int i = 0; i < _max; i++) { builder.CopyTo(0, array, 0, builder.Length); } s1.Stop(); var s2 = Stopwatch.StartNew(); // Version 2: use for-loop. for (int i = 0; i < _max; i++) { for (int z = 0; z < builder.Length; z++) { array[z] = builder[z]; } } s2.Stop(); Console.WriteLine(((double)(s1.Elapsed.TotalMilliseconds * 1000000) / _max).ToString("0.00 ns")); Console.WriteLine(((double)(s2.Elapsed.TotalMilliseconds * 1000000) / _max).ToString("0.00 ns")); 17.46 ns CopyTo 278.59 ns Assign elements
The Equals
method compares the contents of 2 StringBuilders
. It avoids lots of error-prone code that might otherwise be needed. It returns true or false.
We get an ArgumentOutOfRangeException
if we put too much data in a StringBuilder
. The maximum number of characters is equal to Int32.MaxValue
.
Int32.MaxValue
constant is equal to 2,147,483,647. This is the max length of a StringBuilder
.StringBuilder
is mainly a performance optimization—it can reduce copying, allocations and thus garbage collector pressure. It helps optimize many C# programs.