Take advantage of best practices for using StringBuilder to reduce memory allocations and improve the performance of your string operations. Credit: andy.brandon50 Strings are immutable types in .NET. Whenever you modify a String object in .NET, a new String object is created in memory to hold the new data. By contrast, a StringBuilder object represents a mutable string of characters, and expands its memory allocation dynamically as the size of the string grows. The String and StringBuilder classes are two popular classes that you will use frequently when working with strings in .NET Framework and in .NET Core. However, each has its benefits and downsides. In an earlier post here, I discussed how these two classes compare and when one should be used in lieu of the other. In this article I’ll discuss how you can improve the performance of StringBuilder in C#. BenchmarkDotNet is a lightweight, open source library for benchmarking .NET code. BenchmarkDotNet can transform your methods into benchmarks, track those methods, and then provide insights into the performance data captured. We’ll take advantage of BenchmarkDotNet to benchmark our StringBuilder operations in this post. To work with the code examples provided in this article, you should have Visual Studio 2019 installed in your system. If you don’t already have a copy, you can download Visual Studio 2019 here. Create a console application project in Visual Studio First let’s create a .NET Core console application project in Visual Studio. Assuming Visual Studio 2019 is installed in your system, follow the steps outlined below to create a new .NET Core console application project. Launch the Visual Studio IDE. Click on “Create new project.” In the “Create new project” window, select “Console App (.NET Core)” from the list of templates displayed. Click Next. In the “Configure your new project” window shown next, specify the name and location for the new project. Click Create. This will create a new .NET Core console application project in Visual Studio 2019. We’ll use this project to work with StringBuilder in the subsequent sections of this article. Install the BenchmarkDotNet NuGet package To work with BenchmarkDotNet you must install the BenchmarkDotNet package. You can do this either via the NuGet Package Manager inside the Visual Studio 2019 IDE, or by executing the following command in the NuGet Package Manager Console: Install-Package BenchmarkDotNet Use StringBuilderCache to reduce allocations StringBuilderCache is an internal class that is available in .NET and .NET Core. Whenever you have the need to create multiple instances of StringBuilder, you can use StringBuilderCache to reduce the cost of allocations considerably. StringBuilderCache works by caching a StringBuilder instance and then reusing it when a new StringBuilder instance is needed. This reduces allocations because you need to have just one StringBuilder instance in the memory. Let us illustrate this with some code. Create a class called StringBuilderBenchmarkDemo in the Program.cs file. Create a method named AppendStringUsingStringBuilder with the following code: public string AppendStringUsingStringBuilder() { var stringBuilder = new StringBuilder(); stringBuilder.Append("First String"); stringBuilder.Append("Second String"); stringBuilder.Append("Third String"); return stringBuilder.ToString(); } The above code snippet shows how you can use a StringBuilder object to append strings. Next create a method called AppendStringUsingStringBuilderCache with the following code: public string AppendStringUsingStringBuilderCache() { var stringBuilder = StringBuilderCache.Acquire(); stringBuilder.Append("First String"); stringBuilder.Append("Second String"); stringBuilder.Append("Third String"); return StringBuilderCache.GetStringAndRelease(stringBuilder); } The above code snippet illustrates how you can create a StringBuilder instance using the Acquire method of the StringBuilderCache class and then use it to append strings. Here’s the complete source code of the StringBuilderBenchmarkDemo class for your reference. [MemoryDiagnoser] public class StringBuilderBenchmarkDemo { [Benchmark] public string AppendStringUsingStringBuilder() { var stringBuilder = new StringBuilder(); stringBuilder.Append("First String"); stringBuilder.Append("Second String"); stringBuilder.Append("Third String"); return stringBuilder.ToString(); } [Benchmark] public string AppendStringUsingStringBuilderCache() { var stringBuilder = StringBuilderCache.Acquire(); stringBuilder.Append("First String"); stringBuilder.Append("Second String"); stringBuilder.Append("Third String"); return StringBuilderCache.GetStringAndRelease(stringBuilder); } } You must specify the initial starting point now using the BenchmarkRunner class. This is a way of informing BenchmarkDotNet to run benchmarks on the class specified. Replace the default source code of the Main method using the following code: static void Main(string[] args) { var summary = BenchmarkRunner.Run<StringBuilderBenchmarkDemo>(); } Now compile your project in Release mode and run benchmarking using the following command at the command line: dotnet run -p StringBuilderPerfDemo.csproj -c Release Figure 1 below illustrates the performance differences of the two methods. IDG Figure 1. Comparing StringBuilder performance with and without StringBuilderCache. As you can see, appending strings using StringBuilderCache is much faster and needs fewer allocations. Use StringBuilder.AppendJoin instead of String.Join Recall that String objects are immutable, so modifying a String object requires the creation of a new String object. Thus you should use the StringBuilder.AppendJoin method in lieu of String.Join when concatenating strings to reduce allocations and improve performance. The following code listing illustrates how you can use the String.Join and StringBuilder.AppendJoin methods to assemble a long string. [Benchmark] public string UsingStringJoin() { var list = new List < string > { "A", "B", "C", "D", "E" }; var stringBuilder = new StringBuilder(); for (int i = 0; i < 10000; i++) { stringBuilder.Append(string.Join(' ', list)); } return stringBuilder.ToString(); } [Benchmark] public string UsingAppendJoin() { var list = new List < string > { "A", "B", "C", "D", "E" }; var stringBuilder = new StringBuilder(); for (int i = 0; i < 10000; i++) { stringBuilder.AppendJoin(' ', list); } return stringBuilder.ToString(); } Figure 2 below displays the benchmark results of these two methods. Note that for this operation the two methods were close in speed, but StringBuilder.AppendJoin used significantly less memory. IDG Figure 2. Comparing String.Join and StringBuilder.AppendJoin. Append a single character using StringBuilder Note that when using StringBuilder, you should use Append(char) in lieu of Append(String) if you need to append a single character. Consider the following two methods: [Benchmark] public string AppendStringUsingString() { var stringBuilder = new StringBuilder(); for (int i = 0; i < 1000; i++) { stringBuilder.Append("a"); stringBuilder.Append("b"); stringBuilder.Append("c"); } return stringBuilder.ToString(); } [Benchmark] public string AppendStringUsingChar() { var stringBuilder = new StringBuilder(); for (int i = 0; i < 1000; i++) { stringBuilder.Append('a'); stringBuilder.Append('b'); stringBuilder.Append('c'); } return stringBuilder.ToString(); } As evident from the name, the AppendStringUsingString method illustrates how you can append strings using a string as the parameter to the Append method. The AppendStringUsingChar method illustrates how you can use characters in the Append method to append characters. Figure 3 below shows the benchmarking result of the two methods. IDG Figure 3. Comparing Append(char) and Append(String) operations when using StringBuilder. Other StringBuilder optimizations StringBuilder allows you to set the capacity to increase performance. If you know the size of the string you’ll be creating, you can set the initial capacity accordingly to reduce memory allocation considerably. You can also improve StringBuilder performance by using a reusable pool of StringBuilder objects to avoid allocations. These points were already discussed in an earlier article here. Finally, note that, because StringBuilderCache is an internal class, you will need to paste the source code into your project to use it. Recall that you can use an internal class in C# within the same assembly or library only. Hence our program file cannot access the StringBuilderCache class simply by referencing the library in which StringBuilderCache is available. This is why we’ve copied the source code of the StringBuilderCache class into our program file, i.e., the Program.cs file. Related content news Microsoft unveils imaging APIs for Windows Copilot Runtime Generative AI-backed APIs will allow developers to build image super resolution, image segmentation, object erase, and OCR capabilities into Windows applications. By Paul Krill Nov 19, 2024 2 mins Generative AI APIs Development Libraries and Frameworks news Akka distributed computing platform adds Java SDK Akka enables development of applications that are primarily event-driven, deployable on Akka’s serverless platform or on AWS, Azure, or GCP cloud instances. By Paul Krill Nov 18, 2024 2 mins Java Scala Serverless Computing news Spin 3.0 supports polyglot development using Wasm components Fermyon’s open source framework for building server-side WebAssembly apps allows developers to compose apps from components created with different languages. By Paul Krill Nov 18, 2024 2 mins Microservices Serverless Computing Development Libraries and Frameworks how-to How to use DispatchProxy for AOP in .NET Core Take advantage of the DispatchProxy class in C# to implement aspect-oriented programming by creating proxies that dynamically intercept method calls. By Joydip Kanjilal Nov 14, 2024 7 mins Microsoft .NET C# Development Libraries and Frameworks Resources Videos