Interpolated strings: advanced usages

  • .NET

This blog post is part of The First C# Advent Calendar, a series of 25 posts about C#. Be sure to check out the rest of the blog posts in the calendar!

This blog post will show you how to take advantages of the interpolated strings to do more than just a basic string concatenation. Indeed, interpolated strings are often use as an easier way to concatenate strings. For instance:

var fullname = "Gérald Barré";
var nickname = "Meziantou";

var str = fullname + " aka. " + nickname;
var str = string.Format("{0} aka. {1}", fullname, nickname);
var str = $"{fullname} aka. {nickname}"; // Interpolated string is more readable

As with string.Format, you can use a custom format using a colon to separate the value and the format:

var value = 42;
Console.WriteLine($"{value:C}"); // $42.00

Under the hood

The compiler has 3 different ways to convert an interpolated string. Depending on the usage, it will automatically choose the most performant one.

Using string.Concat

If the interpolated string is casted to string and all arguments are of type string, the compiler will rewrite the interpolated string to string.Concat.

string name = "meziantou";
string hello = $"Hello {name}";

The previous code is rewritten by the compiler as:

string name = "meziantou";
string hello = string.Concat("Hello ", name);

Using string.Format

If the interpolated string is casted to string and some of the arguments are not of type string, the compiler will rewrite the interpolated string to string.Format.

DateTime now = DateTime.Now;
string str = $"It is {now}";

The previous code is rewritten by the compiler as:

DateTime now = DateTime.Now;
string str = string.Format("It is {0}", now);

Using FormattableString

If the interpolated string is casted to FormattableString, the compiler rewrites the interpolated string to create a new FormattableString using FormattableStringFactory.Create:

object value1 = "Foo";
object value2 = "Bar";
var str = $"Test {value1} {value2}";

The previous code is rewritten by the compiler as:

object value1 = "Foo";
object value2 = "Bar";
var str = FormattableStringFactory.Create("Test {0} {1}", new object[] { value1, value2 });

The factory creates an instance of ConcreteFormattableString using the format and the arguments. Here's the code of the class:

class ConcreteFormattableString : FormattableString
    private readonly string _format;
    private readonly object[] _arguments;

    internal ConcreteFormattableString(string format, object[] arguments)
        _format = format;
        _arguments = arguments;

    public override string Format => _format;
    public override object[] GetArguments() => _arguments;
    public override int ArgumentCount => _arguments.Length;
    public override object GetArgument(int index) => _arguments[index];

    public override string ToString(IFormatProvider formatProvider)
        return string.Format(formatProvider, _format, _arguments);

The full code source of the factory is available on GitHub in the CoreCLR repo: FormattableStringFactory.cs, FormattableString.cs.

At the end, the ToString method will call string.Format with the arguments and the specified FormatProvider.

Specifying culture

Using the old String.Format, you can specify the culture to use to format the values:

var culture = CultureInfo.GetCultureInfo("fr-FR");
string.Format(culture, "{0:C}", 42); // 42,00 €

The interpolated string syntax doesn't provide a way to directly set the format provider. By default, it use the current culture (source). You can also use the invariant culture by using the FormattableString.Invariant method:

var value = 42;
Console.WriteLine(FormattableString.Invariant($"Value {value:C}")); // Value ¤42.00

// You can simplify the usage of Invariant with  "using static"
using static System.FormattableString;
Console.WriteLine(Invariant($"Value {value:C}")); // Value ¤42.00

If you want to use a specific culture, you'll have to implement your own method (very simple):

private static string WithCulture(CultureInfo cultureInfo, FormattableString formattableString)
    return formattableString.ToString(cultureInfo);

WithCulture(CultureInfo.GetCultureInfo("jp-JP"), $"{value:C}"); // ¥42.00
WithCulture(CultureInfo.GetCultureInfo("fr-FR"), $"{value:C}"); // 42,00 €

That's the basics. Now, let's use the FormattableString class to do some trickier things 😃

Escaping command line arguments

The first example consists in escaping the values to use them as command line arguments. The final result looks like:

var arg1 = "c:\\Program Files\\whoami.exe";
var arg2 = "Gérald Barré";
var commandLine = EscapeCommandLineArgs($"{arg1} {arg2}"); // "c:\Program Files\whoami.exe" "Gérald Barré"

First, install the NuGet package Meziantou.Framework.CommandLine (NuGet, GitHub), a package for building command lines. It follows the rules provided in the very interesting post: Everyone quotes command line arguments the wrong way.

Now, you can escape the arguments of the command line using the CommandLine class, and then call string.Format:

string EscapeCommandLineArgs(FormattableString formattableString)
    var args = formattableString.GetArguments()
                   .Select(arg => CommandLineBuilder.WindowsQuotedArgument(string.Format("{0}", arg)))
    return string.Format(formattableString.Format, args);

This works great in most cases. But it doesn't respect the format of the arguments. For instance, if the string is $"{0:C}", the C format is forgotten. A better way is to create a custom class that implements IFormatProvider. The Format method of the formatter is called once for each argument with their value and format. This, you can process them and output the value with the actual format. The code is a little longer, but it respects the formatting of the arguments:

string EscapeCommandLineArgs(FormattableString formattableString)
    return formattableString.ToString(new CommandLineFormatProvider());

class CommandLineFormatProvider : IFormatProvider
    public object GetFormat(Type formatType)
        if (typeof(ICustomFormatter).IsAssignableFrom(formatType))
            return new CommandLineFormatter();

        return null;

    private class CommandLineFormatter : ICustomFormatter
        public string Format(string format, object arg, IFormatProvider formatProvider)
            if (arg == null)
                return string.Empty;

            if (arg is string str)
                return CommandLineBuilder.WindowsQuotedArgument(str);

            if (arg is IFormattable) // Format the argument before escaping the value
                return CommandLineBuilder.WindowsQuotedArgument(((IFormattable)arg).ToString(format, CultureInfo.InvariantCulture));

            return CommandLineBuilder.WindowsQuotedArgument(arg.ToString());

Executing a SQL query with parameters

Now, let's see how to use interpolated strings to create a parameterized query. Using parameterized query is important for security, as it's a way to protect from SQL injection attacks, and for performance.

The idea is to replace arguments by @p0, @p1 and so on to create the SQL query. Then, you can create the command parameters with the actual values.

using (var sqlConnection = new SqlConnection())
    ExecuteNonQuery(sqlConnection, $@"
UPDATE Customers
SET Name = {"Meziantou"}
WHERE Id = {1}");
void ExecuteNonQuery(DbConnection connection, FormattableString formattableString)
    using (var command = connection.CreateCommand())
        // Replace values by @p0, @p1, @p2, ....
        var args = Enumerable.Range(0, formattableString.ArgumentCount).Select(i => (object)("@p" + i)).ToArray();

        command.CommandType = System.Data.CommandType.Text;
        command.CommandText = string.Format(formattableString.Format, args);

        // Create parameters
        for (var i = 0; i < formattableString.ArgumentCount; i++)
            var arg = formattableString.GetArgument(i);
            var p = command.CreateParameter();
            p.ParameterName = "@p" + i;
            p.Value = arg;

        // Execute the command


Interpolated string is a very nice feature introduced in C# 6. It allows to use all the functionalities of string.Format but with a much nicer syntax. Taking advantages of the format provider allows to write more readable code in some scenario. For instance, you can automatically escape values, or do something totally different from concatenating strings such as executing a SQL query.

Do you have a question or a suggestion about this post? Contact me on Twitter or by email!

Follow me:
Enjoy this blog?Buy Me A CoffeeDonate with PayPal


Gérald Barré -

I didn't know your blog before. I've subscribe to the feed 😃

Honestly, I got inspired by Entity Framework Core 2 which introduced a similar function using FormattableString too:

var users = context.Customers.FromSql($"select * FROM [users] where DateOfBirth > {date}");
James Curran -

It's funny. Marc Gravell touched on this in a blog post a couple days ago also. It must be something in the air…

Buddy Favors Jr -

You didn't even use interpolation in your ExecuteNonQuery! Should be $"@p{i}"

Buddy Favors Jr -

My bad that might break your formatter class, I'm not in front of my computer at this time and it's early.

Buddy Favors Jr -

Interesting, I haven't looked at any benchmarks yet since this feature came out so that's good know. Awesome work!