C# 11 string interpolation

New features of C# 11 in .NET 6 results in super-optimized string interpolation.

Sample

string FormatVersion(int major, int minor, int build, int revision) =>
    $"{major}.{minor}.{build}.{revision}";

Before C# 11

Approximation of what C# 10 would generate:

string FormatVersion(int major, int minor, int build, int revision)
{
    var array = new object[4];
    array[0] = major;
    array[1] = minor;
    array[2] = build;
    array[3] = revision;
    return string.Format("{0}.{1}.{2}.{3}", array);
}

Since C# 11

Approximation of what C# 10 would generate:

string FormatVersion(int major, int minor, int build, int revision)
{
    var handler = new DefaultInterpolatedStringHandler(literalLength: 3, formattedCount: 4);
    handler.AppendFormatted(major);
    handler.AppendLiteral(".");
    handler.AppendFormatted(minor);
    handler.AppendLiteral(".");
    handler.AppendFormatted(build);
    handler.AppendLiteral(".");
    handler.AppendFormatted(revision);
    return handler.ToStringAndClear();
}

Benchmark

C#.NETMeanRatioAllocated
105111.70 ns1.00192 B
11666.75 ns0.6040 B

Note: The results are highly subjective to the machine running the benchmark.

Key improvements

  • No need for object[4] allocation.

  • No need for the 4 int boxing allocations.

  • Now supports writing ref struct types, such as ReadOnlySpan<char>

  • Now supports writing into an existing Span<char> via the new extension method MemoryExtensions.TryWrite, eg:

    Span<char> mySpan = stackalloc char[64];
    var ok = mySpan.TryWrite(null, $"{major}.{minor}.{build}.{revision}", out var charsWritten);
  • String interpolation, including when using aforementioned MemoryExtensions.TryWrite, can take use of newly public-made ISpanFormattable as an allocation-free ToString alternative.

  • StringBuilder.Append and .AppendLine also supports these new string interpolations via ISpanFormattable, meaning the following .Append is also fast and allocation-free:

    var builder = new StringBuilder(); // obviously not allocation-free
    builder.Append($"{major}.{minor}.{build}.{revision}"); // allocation-free!

Under the hood

References