# String Interpolation and StringBuilder in .NET6

In a  previous post I have looked into what are the performance characteristics of creating interpolated string in .NET 5 and early previews of .NET 6. With .NET 6 preview 7 or better a new approach is used by the C# compiler with support from the BCL to build interpolated strings.

Fortunately the new way provides a faster approach for the most convenient ways for assembling strings, however it makes my previous post completely obsolete.

> For details about the new way `DefaultInterpolatedStringHandler` builds strings, read: https://devblogs.microsoft.com/dotnet/string-interpolation-in-c-10-and-net-6/

Re-running the benchmarks from the [previous post](https://blog.ladeak.net/posts/string-interpolation-stringbuilder), shows much better memory usage:

```
BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19043.1165 (21H1/May2021Update)
Intel Core i5-1035G4 CPU 1.10GHz, 1 CPU, 8 logical and 4 physical cores
.NET SDK=6.0.100-preview.7.21379.14
  [Host]     : .NET 6.0.0 (6.0.21.37719), X64 RyuJIT
  DefaultJob : .NET 6.0.0 (6.0.21.37719), X64 RyuJIT


|                   Method |     Mean |    Error |   StdDev |  Gen 0 | Gen 1 | Gen 2 | Allocated | Code Size |
|------------------------- |---------:|---------:|---------:|-------:|------:|------:|----------:|----------:|
|      StringInterpolation | 54.13 ns | 1.125 ns | 1.613 ns | 0.0229 |     - |     - |      72 B |   2,132 B |
|           AppendAllParts | 29.78 ns | 0.653 ns | 0.936 ns | 0.0229 |     - |     - |      72 B |   1,110 B |
|             AppendFormat | 77.67 ns | 1.608 ns | 2.201 ns | 0.0229 |     - |     - |      72 B |   3,351 B |
| AppendInterpolatedFormat | 43.19 ns | 0.934 ns | 1.181 ns | 0.0229 |     - |     - |      72 B |   1,167 B |
|             StringCreate | 18.38 ns | 0.423 ns | 0.633 ns | 0.0229 |     - |     - |      72 B |     730 B |
|    StringArrayPoolCreate | 42.78 ns | 0.914 ns | 1.502 ns | 0.0229 |     - |     - |      72 B |   2,800 B |
```

The two main improvements compared to the previous benchmark are: `StringInterpolation` and `AppendInterpolatedFormat` cases. Both of them allocate less memory, and `AppendInterpolatedFormat` has a significant improvement in execution time.

## StringInterpolation

Let's examine the first example. I used interpolated strings feature:

```csharp
public string StringInterpolation()
{
    return $"{A} {B}, {C}?";
}
```

Previously, C# compiler compiled the following code for the interpolated string example:

```csharp
public string StringInterpolation()
{
    return A + " " + B + ", " + C + "?";
}
```

For the code above, the IL instructions create a new string array for A, B, C and the constant values. Then invokes ```string.Concat``` method to produce the result string.

From .NET 6, this changes to the following:


```csharp
public string StringInterpolation()
{
    DefaultInterpolatedStringHandler defaultInterpolatedStringHandler = new DefaultInterpolatedStringHandler(4, 3);
    defaultInterpolatedStringHandler.AppendFormatted(A);
    defaultInterpolatedStringHandler.AppendLiteral(" ");
    defaultInterpolatedStringHandler.AppendFormatted(B);
    defaultInterpolatedStringHandler.AppendLiteral(", ");
    defaultInterpolatedStringHandler.AppendFormatted(C);
    defaultInterpolatedStringHandler.AppendLiteral("?");
    return defaultInterpolatedStringHandler.ToStringAndClear();
}
```

In this case a non-allocating `ref struct DefaultInterpolatedStringHandler` is used to concatenate the string parts. This avoids all allocations, other than the one required for the desired string object. The compiler also rewrites the code to append the individual code parts as one would do with a `StringBuilder` type. In this example the compiler uses `AppendFormatted` and `AppendLiteral` methods to concatenate strings. The two constructor parameters of `DefaultInterpolatedStringHandler` are `int literalLength, int formattedCount`. 

```csharp
public DefaultInterpolatedStringHandler(int literalLength, int formattedCount)
```

Literal length denotes the number of literal chars appended, here these are `" ", ", ", "?"` which sums up as 4 chars. Formatted count is the number of formatted strings appended. `DefaultInterpolatedStringHandler` rents a char array from an arraypool, to buffer the intermediate results. The default calculation for length of the buffer, uses 11 chars as the default length per formatted strings + literal length. The value is also maximized by 256 chars. It is also specific to .NET release, hence it might change in future releases. Considering the maximum, for longer strings `StringBuilder` with a default capacity might be still beneficial based on the use case.

The C# compiler's new approach of string interpolation costs as much of memory as the alternatives.

## AppendInterpolatedFormat

One of the worst performing code (previous to .NET6) was appending an interpolated string with a string builder's `Append` method taking a string argument. Although this seems very convenient, it was the worst performing solution from memory and from execution time point of view as well. Fortunately, .NET6 changes this.

```csharp
public string AppendInterpolatedFormat()
{
    Builder.Clear();
    Builder.Append($"{A} {B}, {C}?");
    return Builder.ToString();
}
```

The above code is compiled to use another interpolated string handler: `AppendInterpolatedStringHandler`. The idea behind is quite the same as for the `DefaultInterpolatedStringHandler`, except this type works hand-in-hand with a `StringBuilder` object passed as a constructor argument.

```csharp
public string AppendInterpolatedFormat()
{
    Builder.Clear();
    StringBuilder builder = Builder;
    StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(4, 3, builder);
    handler.AppendFormatted(A);
    handler.AppendLiteral(" ");
    handler.AppendFormatted(B);
    handler.AppendLiteral(", ");
    handler.AppendFormatted(C);
    handler.AppendLiteral("?");
    builder.Append(ref handler);
    return Builder.ToString();
}

```

`AppendInterpolatedStringHandler` struct is nested type of `StringBuilder`. It appends the formatted strings and literals directly to the string builder object. A careful reader might wonder what the `builder.Append(ref handler);` line does, if the string is appended to the string builder directly. Currently it just returns the string builder without a side effect.

With this solution, a comparable performance is reached as manually appending the string parts. `AppendInterpolatedStringHandler` will try to Format the appended value into the remaining empty space of the string builder using the `ISpanFormattable` interface. This means that if someone creates a new type which is to be serialized as a string, it worth implementing this interface, which becomes public with .NET6.