One of the best new features of C# 6 was string interpolation. It is much nicer to read, and you no longer had to rely on 3rd party libraries remembering to introduce ‘DoSomethingFormat(string, params object[])’ alongside just ‘DoSomething(string)’.
But ‘DoSomethingFormat’ seemingly has one big advantage over string interpolation when it comes to unit tests – you can assert on particular values direcly, without having to parse them out of an applied string template. If your date or number format suddenly changes, you don’t need to change your Unit Tests (provided, of course, the format itself is not important enough, just the data).
Well, good news – interpolated strings have a rarely mentioned way for you to have the best of both worlds - FormattableString type!
Interpolated strings actually have 3 implicit conversions - String (default), IFormattable and FormattableString. The last one “Represents a composite format string, along with the arguments to be formatted.” And it opens great opportunities for us to provide APIs which allow users to interpolate all they want, but also lets us examine and manipulate raw argument values. For example, Entity Framework Core 2 accepts FormattableString in place of normal string in certain places, which allows it to protect you from dependency injection even when you use interpolation.
To instruct compiler that we want to use FormattableString, we must make sure that result of interpolation is assigned into a variable of this explicit type (as shown above).
One of the uses of FormattableString I found immediately useful is in testing. Let’s say we have a log message important enough that we have to test, that the logger is actually being called. But the template is quite volatile and changes often – we would really like to just test that the call happens and template is applied to correct underlying data. Easy!
While interpolated strings are part of C#6 language, and can be used with any version of .NET, FormattableString is available with .NET framework 4.6 an up.
P.S. As mentioned, this is very usefull with loging. But what if your logger version does not have an overload that accepts FormattableString, and you don’t have time for regression, so don’t want to update it? Quite possibly, your logger interface has an overload that accepts an object (and calls .ToString() on it internally) – just use an explicit cast with your inrepolation.
_log.Info((FormattableString)$"Calculation result:{result}");
P.P.S. Remember, FormattableString represent delayed interpolation, and for reference types will give you their state at the time of actual use.