C# Advent - Better String Formatting with String Interpolation

As part of C# Advent I wanted to call attention to a newer C# language feature that makes me happy every time I use it - String Interpolation.

From the beginning, .NET has supported elaborate string building using String.Format - the ability to construct text blocks at runtime from a combination of static text and the values of variables in your application. The basic syntax of String.Format has been around since C in one form or another. For C# the idea is you can refer to arguments by their ordinal position and insert them multiple times in a single string, along with instructions on how to format them for display, like this:

var userName = "Kendall Miller";
var auditText = string.Format("Last Updated by {0} on {1:d}", userName, DateTimeOffset.Now);

At runtime, .NET will create a new string by inserting the arguments into the static text - in this case the zero-th argument is ‘userName’ and the first is ‘DateTimeOffset.Now’. The former is a string and since we didn’t provide any formatting guidance it’s output as is. The second is a DateTimeOffset and we’ve provided formatting guidance - in this case using the “d” representation to display just the date portion.

Easier to Read and Validate Formatting with String Interpolation

In C# 6.0, String Interpolation was added, replacing arguments referenced by ordinal with the variable names you’d use instead. So, instead of writing this statement:

var userName = "Kendall Miller";
var auditText = string.Format("Last Updated by {0} on {1:d}", userName, DateTimeOffset.Now);

You can just refer to the variables inline in the string, like this:

var userName = "Kendall Miller";
var auditText = $"Last Updated by {userName} on {DateTimeOffset.Now:d}";

By placing $ before the string we’re instructing the compiler to scan for symbols and expressions witin the curly braces instead of ordinal positions. With reasonable variable naming this produces a dramatically easier-to-read block of code.

You can do the same trick with any expression you might use as an argument to string.Format. For example, you can walk properties or have conditional logic, like this:

var message = $"File {(isLocked ? "is" : "isn't" )} locked.  Last Updated by {file.LastUpdateUser}"

Since the colon (:) is reserved in format strings for the formatting expression we wrap the branching logic in parenthesis to clarify our intent.

How’s This Really Work?

Under the covers, the compiler converts an interpolated string into either the equivalent string.Format statement or into a string.Concatenate statement, depending on the complexity of the interpolation. String.Concatenate is significantly faster so it’s used when feasible.

Since this is a compile-time feature, you can’t use it in a scenario where you’re getting the format string at runtime. For example, you take this conditional formatting scenario:

string messageFormat;

if (adminUser) 
    messageFormat = "Unable to access {0}, you can grant yourself access using the {1} menu.";
else
    messageFormat = "Unable to access {0}, contact your site administrator using the {1} menu.";

var message = string.Format(messageFormat, pageUrl, supportMenu);

Of course, this could be represented as independent string interpolation statements which is easier to read anyway:

string message;

if (adminUser) 
    message = $"Unable to access {pageUrl}, you can grant yourself access using the {supportMenu} menu.";
else
    messaget = $"Unable to access {pageUrl}, contact your site administrator using the {supportMenu} menu.";

Because String Interpolation compiles down to String.Format it’s still necessary to guard against null reference exceptions. For example, the following will throw an exception:

FileInfo currentFile = null;
var message = $"Opening File '{currentFile.Name}'..";

Which you can avoid by using the null propagation operator:

FileInfo currentFile = null;
var message = $"Opening File '{currentFile?.Name}'..";

Rock solid centralized logging

Unlimited applications, unlimited errors, scalable from solo startup to enterprise.