Boost your C# Application with Hidden Power of StringBuilder over String

Özkan ARDİL
7 min readJun 1, 2024

--

Boost your C# applicaiton using StringBuilder over string efficiently. Discover best practices, performance comparison and hacks for superior results.

String vs StringBuilder in C#: Detailed Comparison and Best Practices

Working with text in the C# programming language is a fairly common requirement.

At this point, we come across two basic classes for representing and manipulating texts: string and StringBuilder.

Both classes provide text manipulation, but they differ significantly in terms of performance and memory management.

In this article, I will;

  • Compare the String and StringBuilder classes in detail,
  • Examine the properties of each, and explain which class is more appropriate to use in which situations,
  • I will test the performance of these two classes through an example scenario.

If you’re ready, let’s get started…

String Data Type in C#

In C#, string is a data type used to store text. It is an instance of the System.String class and is a predefined type in C#. It retains Unicode character sequences, which means it supports characters from a wide range of languages.

The type in C# is an immutable data type. This means that an object cannot be modified after it has been created. For example, when you want to change a value, a new object is actually created, and the old object is purged from memory (garbage collected).

For example, consider the following code.

string str = "Hello";
str = "World";

In this code, an object with a value is created. On the second line, a new object is created with a value, and the variable no longer points to this new object. The first object will be cleared from memory if it has no other references.

Then if we sum up the properties of the string data type;

  • String is an element of the System.String class,
  • It is immutable,
  • We can say that it holds Unicode character sequences.

StringBuilder Data Type in C#

In C#, StringBuilder is a class in the System.Text namespace that is used for text manipulations. StringBuilder provides high performance and efficient memory usage, especially when working on text that changes and merges frequently.

StringBuilder is mutable, which means that changes made to it are performed on the existing object, and a new object is not created each time. This provides a performance advantage, especially when large and frequent changes are made.

It is quite simple to perform basic operations withStringBuilder.

I share an example of the use of the class below.

using System.Text;

StringBuilder sb = new StringBuilder("Hello");
sb.Append(" World");
sb.AppendLine("!");
sb.AppendFormat(" Today is {0}.", DateTime.Now.DayOfWeek);
string result = sb.ToString();

Character Replacement and Deletion with StringBuilder

StringBuilder sb = new StringBuilder("Hello World!");
sb[6] = 'K';
sb.Remove(5, 1); // "Hello World!" -> "Hello Korld!"
sb.Insert(5, ' '); // "Hello World!" -> "Hello K orld!"
string result = sb.ToString();

Capacity Management with StringBuilder

StringBuilder sb = new StringBuilder(50); // Initial capacity is 50
sb.Append("Merhaba");
int capacity = sb.Capacity; // 50
int length = sb.Length; // 7
sb.EnsureCapacity(100); // Increase the capasity to 100

As can be seen from the example uses, the StringBuilder class offers us a flexible structure for the management of large text contents.

Then if we sum up the properties of the StringBuilder data type;

  • StringBuilder is an element of the System.Text class,
  • It is mutable,
  • We can say that it offers us a flexible structure for the management of large text contents.

Of course, there is also a performance and resource usage dimension to the event.

Perhaps these differences can be ignored in small textual operations, but I think it will be beneficial to take them into account in large projects.

So let’s take a look at the event from a performance point of view.

String vs. StringBuilder Performance Comparison

For this, I will write a simple but straightforward test using its library and with which I can compare the performance of its classes. BenchmarkDotNetstringStringBuilder

Below you see the code I added to the console app’s file.Program.cs

var result = BenchmarkRunner.Run<TestClassStringVsStringBuilder>();

[MemoryDiagnoser]
public class TestClassStringVsStringBuilder
{
private const int N = 1000;

[Benchmark]
public string StringConcatenation()
{
string result = "";
for (int i = 0; i < N; i++)
{
result += "Welcome";
}
return result;
}

[Benchmark(Baseline = true)]
public string StringBuilderConcatenation()
{
var sb = new StringBuilder();
for (int i = 0; i < N; i++)
{
sb.Append("Welcome");
}
return sb.ToString();
}
}

With this code;

I start the test with the Run method of the BenchmarkRunner class.

I added two methods to the “TestClassStringVsStringBuilder” test class.

- StringConcatenation: combines the word "Hello" 1000 times.

- StringBuilderConcatenation: combines the word "Hello" 1000 times.

By placing the “[MemoryDiagnoser]” “attribute” just before the “TestClassStringVsStringBuilder” class, I ensured that the test output also shows results related to “memory” usage.

While the “[Benchmark]” tag allows us to mark the method to be considered in the test results, the “[Benchmark(Baseline = true)]” tag determines which method will be taken as the base for the percentage comparison of the results. After writing the test application, the next step is to run the test.

In order to see the test result, it is necessary to run the application in Release mode. BenchmarkDotNet will make comparisons and display the results on the console screen.

The output will include how much time was spent on both methods, as well as other details such as memory usage.

Here are the test results…

The result of the performanca test

Let me explain what each column in the BenchmarkDotNet output means:

Interpretation of Performance Test

General Information

It contains information about the version of BenchmarkDotNet, the operating system, hardware information, and the .NET SDK version:

  • BenchmarkDotNet v0.13.12: The version of BenchmarkDotNet used.
  • Windows 11: Operating system.
  • 13th Gen Intel Core i7–13700H: Processor info.

.NET SDK 8.0.205: The version of the .NET SDK used.

Table Columns

  1. Method:

Names of the methods tested. Here and their methods are compared. StringConcatenation and StringBuilderConcatenation

Mean:

It shows the average time. Specifies the average time it takes for each test to be completed. There’s a big difference, isn’t there?

Example:

StringConcatenation: 234.274 microseconds (us)

StringBuilderConcatenation: 1.805 microseconds (us)

Error:

Indicates the measurement error. This is the standard error value and indicates the reliability of the measurements.

Example:

StringConcatenation: 1.816 microseconds (us)

StringBuilderConcatenation: 0.0283 microseconds (us)

StdDev (Standard Deviation):

Indicates the standard deviation. It refers to how much the measurements deviate from the mean.

Example:

StringConcatenation: 1.4136 microseconds (us)

StringBuilderConcatenation: 0.0250 microseconds (us)

Ratio:

Shows the comparative ratio. The duration of the fastest method is taken as 1 and the ratio of other methods to this time is calculated.

Example:

StringConcatenation: 129.75

StringBuilderConcatenation: 1.00 (this is the fastest method and is taken as a reference)

RatioSD (Ratio Standard Deviation):

It shows the standard deviation of the ratio.

Example:

StringConcatenation: 2.26

StringBuilderConcatenation: 0.00

Gen0:

Generation 0 indicates the number of garbage collection operations. This specifies the number of times ephemeral objects are cleaned up during the garbage collection process.

Example:

StringConcatenation: 560.3027

StringBuilderConcatenation: 2.4796

Gen1:

Generation 1 indicates the number of garbage collection. This indicates that objects that survived generation 0 and lasted slightly longer are being cleaned up.

Example:

StringConcatenation: 19.7754

StringBuilderConcatenation: 0.1450

Allocated:

Indicates the total amount of allocated memory. This is usually in kilobytes or megabytes.

Example:

StringConcatenation: 6867.15 KB

StringBuilderConcatenation: 30.4 KB

Alloc Ratio:

Indicates the memory allocation ratio. The minimum memory allocation is taken as 1, and the ratio of other methods to this allocation is calculated.

Example:

StringConcatenation: 225.90

StringBuilderConcatenation: 1.00 (this is the lowest memory allocation and is taken as a reference)

If I evaluate these values collectively;

Mean: The StringBuilderConcatenation method is much faster than the StringConcatenation method (1,805 us vs. 234,274 us).

Error and StdDev: The measurements of the StringBuilderConcatenation method are more consistent and have less margin of error.

Ratio and RatioSD (Standard Deviation of Ratio and Ratio): StringBuilderConcatenation has a ratio of 1.00 as the fastest method. StringConcatenation is about 129.75 times slower.

Gen0 and Gen1 (Garbage Collection Operations): StringBuilderConcatenation method triggers far fewer garbage collection operations, which means it’s more efficient in terms of memory management.

Allocated: The StringBuilderConcatenation method allocates much less memory (30.4 KB vs. 6867.15 KB), which is a huge advantage in memory usage.

Alloc Ratio: the StringConcatenation method uses much more memory than StringBuilderConcatenation the method (225.90 vs. 1.00).

Usage Scenarios:

String is usable when;

When a small number of changes are made to small text,

When the readability and simplicity of the code are at the forefront,

StringBuilder is usable when;

When large texts are modified frequently.

In cases where performance is critical (for example, when frequent text merges are performed within loops).

It is possible to say that it will be useful to use it when memory usage needs to be optimized.

Source Code

You can access the source code from my project on Github. I would be very pleased if you give stars to the project and follow me.

--

--

Özkan ARDİL

.NET C# JS and Angular dev with 8+ yrs exp, self-taught & passionate web developer. Sharing tips & experiences in C# and web dev.