Take advantage of these best practices when working with strings in .NET Core for the optimal performance of your applications. Credit: David Lofink Two popular classes that you will use frequently when working with strings in .NET Core are the String and StringBuilder classes. You should be aware of the best practices when using both these classes to build applications that minimize allocations and are highly performant. This article discusses the best practices we can follow when working with strings in C#. 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. Note we’ll also use BenchmarkDotNet to track performance of the methods. If you’re not familiar with BenchmarkDotNet, I suggest reading this article first. Benchmarking code is essential to understanding the performance of your application. It is always a good approach to have the performance metrics at hand when you’re optimizing code. The performance metrics will also help you to narrow in on the portions of the code in the application that need refactoring. In this article they will help us understand the performance of string operations in C#. Create a .NET Core console application project in Visual Studio First off, 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, specify the name and location for the new project. Click Create. We’ll use this project to work with the String and StringBuilder classes 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 String concatenation using String and StringBuilder in C# An immutable object is one that cannot be modified once it has been created. Since a string is an immutable data type in C#, when you combine two or more strings, a new string is produced. However, whereas a string is an immutable type in C#, StringBuilder is an example of a mutable item. In C#, a StringBuilder is a mutable series of characters that can be extended to store more characters if required. Unlike with strings, modifying a StringBuilder instance does not result in the creation of a new instance in memory. When you want to change a string, the Common Language Runtime generates a new string from scratch and discards the old one. So, if you append a series of characters to a string, you will recreate the same string in memory multiple times. By contrast, the StringBuilder class allocates memory for buffer space and then writes new characters directly into the buffer. Allocation happens only once. Consider the following two methods: [Benchmark] public string StringConcatenationUsingStringBuilder() { StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < NumberOfRuns; i++) { stringBuilder.AppendLine("Hello World" + i); } return stringBuilder.ToString(); } [Benchmark] public string StringConcatenationUsingString() { string str = null; for (int i = 0; i < NumberOfRuns; i++) { str += "Hello World" + i; } return str; } Figure 1 shows the performance benchmarks of these two methods. IDG Figure 1. As you can see in Figure 1, concatenating strings using StringBuilder is much faster, consumes much less memory, and uses fewer garbage collections in all generations, compared to using the ‘+’ operator to combine two or more strings. Note that regular string concatenations are faster than using the StringBuilder but only when you’re using a few of them at a time. If you are using two or three string concatenations, use a string. StringBuilder will improve performance in cases where you make repeated modifications to a string or concatenate many strings together. In short, use StringBuilder only for a large number of concatenations. Reduce StringBuilder allocations using a reusable pool in C# Consider the following two methods — one that creates StringBuilder instances without using a pool and another that creates StringBuilder instances using a reusable pool. By using a reusable pool, you can reduce allocations. When you need a StringBuilder instance, you can get one from the pool. When you’re done using the StringBuilder instance, you can return the instance back to the pool. [Benchmark] public void CreateStringBuilderWithoutPool() { for (int i = 0; i < NumberOfRuns; i++) { var stringBuilder = new StringBuilder(); stringBuilder.Append("Hello World" + i); } } [Benchmark] public void CreateStringBuilderWithPool() { var stringBuilderPool = new DefaultObjectPoolProvider().CreateStringBuilderPool(); for (var i = 0; i < NumberOfRuns; i++) { var stringBuilder = stringBuilderPool.Get(); stringBuilder.Append("Hello World" + i); stringBuilderPool.Return(stringBuilder); } } Figure 2 shows the performance benchmarks of these two methods. IDG Figure 2. As you can see in Figure 2, the memory allocation decreases considerably when you use a reusable pool. Extract strings using Substring vs. Append in C# Let us now compare the performance of the Substring method of the String class vs. the Append method of the StringBuilder class for extracting a string from another string. Consider the following piece of code that illustrates two methods — one that extracts a string from another string using the Substring method of the String class and one that does the same using the Append method of the StringBuilder class. [Benchmark] public string ExtractStringUsingSubstring() { const string str = "This is a sample text"; StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < NumberOfRuns; i++) { stringBuilder.Append(str.Substring(0, 10)); } return stringBuilder.ToString(); } [Benchmark] public string ExtractStringUsingAppend() { const string str = "This is a sample text"; StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < NumberOfRuns; i++) { stringBuilder.Append(str, 0, 10); } return stringBuilder.ToString(); } Figure 3 shows the performance benchmarks of these two methods. IDG Figure 3. As you can see in Figure 3, using the Append method of the StringBuilder class to extract a string is both faster and consumes fewer resources than using Substring. Join strings using String.Join vs. StringBuilder.AppendJoin in C# When you’re joining strings, use StringBuilder.AppendJoin in lieu of String.Join for reduced allocations. Consider the following code snippet that illustrates two methods — one that joins strings using String.Join and the other that does the same using StringBuilder.AppendJoin. [Benchmark] public string JoinStringsUsingStringJoin() { var stringBuilder = new StringBuilder(); for (int i = 0; i < NumberOfRuns; i++) { stringBuilder.Append(string.Join("Hello", ' ', "World")); } return stringBuilder.ToString(); } [Benchmark] public string JoinStringsUsingAppendJoin() { var stringBuilder = new StringBuilder(); for (int i = 0; i < NumberOfRuns; i++) { stringBuilder.AppendJoin("Hello", ' ', "World"); } return stringBuilder.ToString(); } Figure 4 shows the performance benchmarks of these two methods. IDG Figure 4. As when extracting a string from a string, StringBuilder offers advantages over the String class when joining strings. As you can see in Figure 4, using AppendJoin of the StringBuilder class is again faster and more resource efficient than using Join of the String class. When using StringBuilder, you can also set the capacity of the StringBuilder instance to improve performance. If you know the size of the string you will be creating, you can set the initial capacity when creating a StringBuilder instance. This can reduce the memory allocation considerably. On a final note, the String.Create method is yet another way to improve string handling performance in .NET Core. It provides an efficient way to create strings at runtime. I’ll discuss String.Create in a future post here. Related content feature 14 great preprocessors for developers who love to code Sometimes it seems like the rules of programming are designed to make coding a chore. Here are 14 ways preprocessors can help make software development fun again. By Peter Wayner Nov 18, 2024 10 mins Development Tools Software Development feature Designing the APIs that accidentally power businesses Well-designed APIs, even those often-neglected internal APIs, make developers more productive and businesses more agile. By Jean Yang Nov 18, 2024 6 mins APIs Software Development 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 news Go language evolving for future hardware, AI workloads The Go team is working to adapt Go to large multicore systems, the latest hardware instructions, and the needs of developers of large-scale AI systems. By Paul Krill Nov 15, 2024 3 mins Google Go Generative AI Programming Languages Resources Videos