Improving String Performance in .NET

.Net Tutorials

In this .NET programming tutorial, we will talk about how we can improve the performance of an application by optimizing string handling. We will also learn why benchmarking code is essential. Developer will further learn how to work with BenchmarkDotNet in the next post in this series, so be sure to check back often on this site.

Read: Working with Strings in C#

String Handling in C#

When working on applications in .NET or .NET Core, developers will frequently have to work with strings. In many cases, programmers will need to manipulate strings that may include creating new strings or adding strings. Inefficient string handling is one of the major bottlenecks as far as the performance of an application is concerned.

A String object is immutable, meaning that it cannot be modified. Every operation that appears to modify a String actually creates and returns a new String object, leaving the original String object unchanged. However, Strings are reference types and thus behave differently than value types.

When you assign one String variable to another, only a reference copy of the handle is made. This is in contrast to value types such as ints, which make copies of the actual data. The two variables now refer to the same memory location; if one is changed, so is the other.

Strings vs StringBuilders in C#

An immutable object is defined as one that cannot be altered once it is created. Because a string is an immutable data type in C#, combining two or more strings results in the creation of a new string. For example, if you add a string instance to another, you are actually creating a new string.

When you update a string, the runtime creates a new sting again. As a result, if you add a string to another string, you will create an additional string instance in the memory. A StringBuilder in C# is a mutable sequence of characters that may be expanded to hold additional characters if necessary.

The StringBuilder class, allocates memory for a buffer and then writes new characters into that buffer. Allocation occurs just once. Modifying a StringBuilder instance, unlike strings, does not result in the creation of a new instance in memory.

The following C# code example shows how you could use a StringBuilder instead of creating new strings each time:

public static string ConcatenateStrings() 
{ 
var builder = new StringBuilder(); 
builder.Append("A "); 
builder.Append("B"); 
builder.Append("C"); 
return builder.ToString(); 
}

If you are concatenating strings in a loop, it is best to use the StringBuilder class. It is faster than using operators or methods in the String class. Actually, it can be twice as fast as using string concatenation, as showm in this code example:

public string StringConcatenationInsideALoop()
{
    StringBuilder builder = new StringBuilder();
    for (int i = 0; i < NumberOfRuns; i++)
    {
         builder.AppendLine(i.ToString());
    }
  return stringBuilder.ToString();
}

Read: C# Tools for Code Quality

Set the Capacity of StringBuilder to Reduce Allocations in C#

An important characteristic of a StringBuilder is its capacity property. The capacity property represents the number of characters in the current instance that are allocated internally to store information in memory.

While using StringBuilder, you may configure the “Capacity” property of a StringBuilder instance to optimize efficiency. If you know the length of the string you are going to create, you may set the initial capacity of the StringBuilder instance when creating it. This will significantly reduce memory allocation and hence improve application performance.

Use a Pool of StringBuilder Instances

You can take advantage of a reusable pool of StringBuilder instances to improve your application’s performance. This can be achieved by reusing an instance of a StringBuilder from the pool. The following C# code snippet shows how you can do this:

   var pool = new DefaultObjectPoolProvider().CreateStringBuilderPool();
   for (var i = 0; i < 100; i++)
   {
       var builder = pool.Get();
       builder.Append("Hello World" + i);
       builderPool.Return(builder);
   }

Use Append.Join for Joining Strings in C#

When you are joining strings in your application, make sure that you leverage StringBuilder.Join and not the String.Join method. The StringBuilder.Join method, when used on multiple strings, will reduce allocations considerably.

The following C# code example illustrates how the AppendJoin method can be used:

public string JoinStrings()
{
    var builder = new StringBuilder();
    for (int i = 0; i < 100; i++)
    {
       builder.AppendJoin("Joining", ' ', "Strings", ' ', 
       "Is", ' ', "Costly");
    }
  return builder.ToString();
}

Reduce Allocations using StringBuilderCache in C#

You can reduce allocations further by using an internal class named StringBuilderCache. Surprisingly, very few people know that this class has been a part of .NET Framework for a very long time. It is also available in the newer versions of .NET Framework, and .NET Core. The StringBuilderCache class works by creating an instance of the StringBuilder when requested by the calling code and then leasing it for use. Your calling code should use this instance and return it back when done. As a result, you would be using just one instance of the StringBuilder in most cases.

The following code snippet shows how the StringBuilderCache can be used in C#:

string str = StringBuilderCache.GetStringAndRelease(stringBuilderInstance);

Read: Productivity Tools for .NET Developers

Benchmark Performance using BenchmarkDotNet

Benchmarking code is critical for understanding your application’s performance. It is usually prudent to keep performance metrics in mind when optimizing code. The performance measurements will also assist you in narrowing down the areas of the application’s code that need reworking.

BenchmarkDotNet allows C# coders to easily compare multiple algorithms, measure execution time, and allocate memory for reference types or value types in .NET programs. Using benchmarking is an easy way to figure out which algorithm or approach would perform better in terms of speed or memory consumption.

You can benchmark the performance of strings in your application using BenchmarkDotNet. BenchmarkDotNet is an open-source tool that allows developers to create and compare the performance of code across different platforms and processors. BenchmarkDotNet can be used for benchmarking the execution time and memory usage of methods and classes in your code, by running each method/class through a set of tests.

Final Thoughts on String Performance in .NET

Regular string concatenations work faster than StringBuilder, but only when you use a few of them at once. Use a string if you’re concatenating a few strings. StringBuilder can improve the application’s performance when you alter several strings or concatenate numerous strings together. Note that the String.Create method introduced recently, can help you improve your application’s performance by optimizing the resource usage when working with strings. Use these recommended practices when dealing with strings in.NET Core to ensure that your application is high performant.

Read more .NET programming tutorials and software development guides.

Joydip Kanjilal
Joydip Kanjilal
A Microsoft Most Valuable Professional in ASP.NET, Speaker, and Author of several books and articles. More than 25 years of experience in IT with more than 18 years in Microsoft .NET and its related technologies. He was selected as a Community Credit Winner at http://www.community-credit.com several times. He has authored 8 books and more than 500 articles in some of the most reputed sites worldwide including MSDN, Info World, CodeMag, Tech Beacon, Tech Target, Developer, CodeGuru, and more.

More by Author

Must Read