.NET 6 string concatenation performance benchmarks

Since there’s been so many amazing performance improvements in .NET 6 I thought it would be fun to compare different string concatenation approaches to see which is the best performing and which is the most memory efficient.

Below are benchmarks taken using BenchmarkDotNet for a simple scenario of just adding 4 strings with spacers and a scenario where we are adding a lot of variable width strings. I’ve included .NET Core 3.1 and .NET 5 benchmarks for reference too.

Benchmarks were taken on a 16GB ram Azure VM… the .NET 6 version was RC2…

The benchmarks show results in nanoseconds and microseconds which are tiny slices of time.

Remember don’t micro-optimise if you don’t need to… if you’re only doing simple concat and not doing it in a hot loop… as always focus on whichever approach is MOST READABLE FIRST… BUT if you have a need to optimise to this level the below benchmarks should guide you.

Concatenating a small amount of strings

For the simple scenario of concatenating just a few strings String.Join is the clear winner in terms of speed, while string interpolation is super efficient in terms of memory allocation. 

Note the significant performance increases between .NET 5 and .NET 6 for String.Format, string interpolation and String.Join. We can also see that memory allocation in .NET 6 for the string interpolation approach is less that half of what it was in .NET 5… AWESOME!!!

Concatenating a large amount of strings from an array or list

When we are concatenating a large amount of random width strings we know the plain string concatenation (+=) approach is not ideal as its performance and memory efficiency is quite poor.

Quite often we first hear about StringBuilder as a more performant alternative to plain string concat and indeed its performance is better than plain string concatenation. For scenarios however where we have a known set of strings in an array for example StringBuilder is not the best option as other approaches such as using String.Concat or String.Join are more suited. This is because these methods can determine final string length ahead of time by examining the collection.

In the below benchmarks where I’m appending 200 strings from a string array we can see that using String.Concat is the fastest approach, with String.Join not far behind. Note the very high memory allocation for the plain string concatenation approach.

I’ve included StringBuilder benchmarks for reference but note the best use case for StringBuilder is for when we do not know the final length of the string and/or how many append operations we will have to do.

Recreating the benchmarks

If you’d like to recreate the benchmarks to see if you get similar results the classes and random string file are below…

StringConcatSimple
StringConcatBenchmarksRandom
RandomString200

How to use Azure Cache for Redis in a .NET Core web app

If you need a distributed cache and are planning on deploying your .NET Core web app to Azure, using Azure Cache for Redis as your cache provider is a good choice.

The steps below show you how to get started…

Create an Azure Cache for Redis instance using the portal

Search for ‘azure cache for redis’ on the portal and on the create screen fill in details such as resource group, DNS name, location and cache type.

Choose the same location as whatever app will be consuming your cache to minimise latency. When you’re just getting setup I’d recommend choosing the Basic C0 cache as this is the cheapest and you can always upgrade later. For all the other tabs just accept the defaults for the moment.

Create a new Redis cache in the portal

After Azure has finished creating your new cache, navigate into it and go to the Access keys page as shown below.

Copy the primary connection string somewhere as you’ll need to put this into your appsettings.json file in the next step.

Viewing Azure Redis access keys

Setup and configure your app

Install Microsoft.Extensions.Caching.StackExchangeRedis from NuGet.

Amend Startup.cs…

Amend startup.cs to use caching

Add a connection string pointing to your Redis instance on Azure into your appsettings.json file. Remember you can get this from the Access keys page as shown above. In our real apps we’d likely store this value in Azure Key Vault.

appsetting-redis-cache

Create a cache wrapper or helper class

At this stage your app should be able to set and read items in Azure Redis by instantiating a concrete instance of the IDistributedCache instance. The next step is to create a simple thin wrapper around this interface. It’s not 100% needed but it helps keep code that is using the cache a little bit neater.

Since .NET Core 3.0 Microsofts default JSON deserializer has been System.Text.Json, however it doesn’t support circular references so in the below example I’ve fallen back to using Newtonsoft.

Note… according to Microsoft System.Text.Json will support circular references from .NET 6.0.

Cache wrapper

Inject IDistributedCache into your controller or service class

Since dependency injection is built into .NET Core and because we’ve registered the Redis Cache service in our Startup.cs file we can inject an instance of the IDistributedCache into our controller class…

Inject IDistributedCache

Check the cache when retrieving data

Finally to use the cache when getting data… we can implement the cache-aside pattern. In this pattern we…

First check the cache for a particular key.
If it does not exist we read data from the DB and store it in the cache.
If it does exist in cache we use the cached version of the data.

Below I’m checking the cache for a list of products…

Example of cache-aside pattern

In this case I’ve not set any DistributedCacheEntryOptions meaning items will not expire. If you need to set absolute or sliding expirations on items you can set the relevant options when adding them to the cache.

Remove items from cache after they’ve been updated

When some data change occurs which means previously cached items are now stale we can remove items from the cache as below…

Invalidate items in cache by removing them

In this case the list of products I previously cached is no longer valid as I’ve deleted one of them.

Depending on the situation we might not have to explicitly remove certain items from the cache like above. In many cases some staleness is acceptable so in this case we might set a sliding expiration of 1 hour or depending on the data just re-cache certain items overnight.

How to test your app is storing items in and reading from Azure Cache for Redis

To confirm everything is working as expected we can run the monitor command in the Redis console direct on the Azure portal.

Override the default controller scaffold templates in Visual Studio 2019 and .NET 5.0

The real power of scaffolding in Visual Studio comes from the ability to override the templates which are used to generate the controllers and views. This gives us complete control of the C# and HTML which is emitted by the scaffolding process.

The templates for Visual Studio 2019 and .NET 5.0 are stored in C:\Users\USERNAME\.nuget\packages\microsoft.visualstudio.web.codegenerators.mvc\5.0.2 as shown below…

Where to find scaffold templates

We can update these templates directly in the above location but this is problematic as it is local to a developers machine and therefore not easily shared with other team members and because changes made here would affect all projects on the machine which may not be desired.

A better way is to copy the templates into our MVC projects like below so they can be checked into source control and shared across the team.

Required folder structure for custom scaffolding

This works as by convention Visual Studio will look for a Templates folder in the MVC project first before using the machine level folder.

Note… you may see build errors related to the templates you’ve just copied into your project before you first try to scaffold. These will go away when Microsoft.VisualStudio.Web.CodeGeneration.Design is installed which Visual Studio will do as part of the scaffold process.

Scaffolding multiple controllers at once from the command line

After you’ve customised your templates you can rescaffold your controllers one by one manually from the GUI, but a better way is to call the asp.net core scaffolding engine from a bat file.

Enabling view refresh after .CSHTML changes for new projects in Visual Studio

Since .NET Core 3.0 your views in MVC won’t refresh after you change your .CSHTML markup. You can enable this for existing projects by installing Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation and setting AddRazorRuntimeCompilation() in your Startup.cs -> ConfigureServices method…

… and …

for new projects you can check the ‘Enable Razor runtime compilation’ checkbox for 3.1 projects onwards when creating the projects.

Enable view refresh for new projects in Visual Studio

Opening web apps in Incognito mode from Visual Studio

If you’re working on a web app in Visual Studio and want to launch it in Incognito mode to make sure you have a 100% reset state you can do this from the ‘Browse With…’ option which is next to the run button. From here you can add a browser with any supported launch switches you desire such as ‘–incognito’ (shown below) or perhaps ‘–disable-extensions’ to speed browser load time up.

We can see on Chromium based browsers that there is a lot of command line switches available and of course they can be combined.

Opening web apps in Incognito mode