With C# 8 on our doorstep, I figure it is a good time to reflect on recent additions to the language that have come before. There are some great improvements you may have missed, some that I really enjoy using, and some I consider have reached canonical usage status that I think are all worth some reflection.

String Interpolation is a great feature that I now use everywhere I can (Except logging - see below).

We have efficient libraries to format strings for us, so we don’t have to concatenate all the time. This is both convenient and performant.

var string1 = "My name is " + "Mark";
var string2 = String.Format("My name is {0}", "Mark");

The resulting string above is equivalent but there are many benefits to the second option:

  • faster internal string building for larger strings with multiple parameters
  • Lazy execution means you can pass template + args down the stack and format later (or never as an optimisation when necessary such as Debug/Verbose logs)
  • Potentially more readable as a template than the concatenation would be (depending on the message).

The problem becomes we have all these index positions. It is very easy with many arguments to either get the positions wrong or to change it and make a mistake.

String Interpolation

You can think of String Interpolation as the compiler automatically turning your string into a String.Format for you. (Oversimplification but useful way to conceptualise it.) The first piece of magic is the $ character. Adding this to the start of a string makes it into an interpolated string. This now grants special power to the braces ({}). When you have a block of braces inside the string, this essentially becomes an expression. You can put whatever code you want in here, and it becomes a format argument. This gives improved readability.

var oldWay = String.Format($"{0} is {1} years old.", p.Name, p.Age);
var newWay = $"{p.Name} is {p.Age} years old.";
  • Mistakes are easier to spot
  • compile-time checks performed on the string and its format
  • performant - same benefits of String.Format applied for optimisations
  • readable - reads like a sentence with clearing understanding of where the arguments come from

You can still use { in your string, you just have to escape it with another {:

var name = "bob";

// This becomes: {"name":"bob"}
var json = "{{""name"":""{name}""}}";

This is a canonical language feature, and you will expect to see most string-related code using it. That is if you are not already yourself.

Why “Except Logging”?

Turns out, there is a massively useful feature with logging using Microsoft.Extensions.Logging and ILogger. Semantic Logging, also known as Structured Logging.

Semantic/Structured Logging supported is up to the implementor of the logging, but Serilog, Application Insights, NLog and others tend to support this feature, and it is defined as a first-class part of the API.

The main difference to String.Format is you give named parameters in the template rather than numbered.

String.Format("My name is {0}", "Mark");
_logger.LogInformation("My name is {FirstName}", "Mark");

Anyone who has actually monitored their logs knows that templates are really valuable. You can find all instances of a log message quickly with templated arguments, rather than having to do a wildcard search with inlined values because each message is a unique snowflake. Most logging frameworks add the arguments as named metadata, using the value from the string (in the above example that would be FirstName).

This works great. As long as you don’t use the new and useful String Interpolation when you log. String Interpolation results in a string passed to the logger, rather than passing a template and arguments, so it’s one or the other at this time.

As you would expect, people really want this to reverse-engineer from String Interpolation into Semantic Logging automatically, but the argument is that it is both unexpected and a breaking change so you probably won’t see that anytime soon without a custom library or API involved.