Very early in the history of programming, we’ve seen the need to use text on a machine that works with numbers. Over many decades we devised many different ways to construct and analyze text for better understanding by both humans and machines. String interpolation, in the way that C# offers it, is the most elegant and readable way to construct text messages we’ve seen so far.

To download the source code for this article, you can visit our GitHub repository.

We already wrote about different ways to construct strings in How to Add Parameters to a String in C#, Using Variables Inside Strings in C#, and A Few Different Ways to Concatenate Strings in C#.

Now we will take a closer look at string interpolation.

Basic C# String Interpolation

String interpolation is a more readable variant of string.Format that was introduced in C# 6. It is identified by $, right before the quotation mark: $"Example". Unlike string.Format, instead of index into an argument array, it uses interpolation expression in a very similar format: 

{<interpolationExpression>[,<alignment>][:<formatString>]}

Alignment and format string work the same as in string.Format. Instead of an index, we can use the variable or any valid C# expression that produces the result which will be converted to a string and inserted in that position.

Simple Use Case

Let’s see a simple example. If we define a BasicStrings class and a single method:

public static string SimpleString(string name, string item)
{
    return $"Hey {name}, where you goin' with that {item} in your hand?";
}

We can use it to show a message on the console:

Console.WriteLine(BasicStrings.SimpleString("Joe", "bun"));

This will produce a new output:

Hey Joe, where you goin' with that bun in your hand?

Interpolated String Constants

Starting from C# 10, we can use string interpolation to define string constants. Any expression used for that must be a constant string:

private const string World = "world";
public const string ConstantString = $"Hello {World}!";

Similarly, we can use this constant:

Console.WriteLine(BasicStrings.ConstantString);

This will show Hello world! string in the console.

Using Alignment and Format String

Getting back to our example BasicStrings class, we can add two more methods:

public static string ThisManyBottlesOfBeerOnTheWall(int bottles)
{
    return $"{bottles} bottles of beer on the wall, {bottles} bottles of beer.";
}

public static string DisplayTableCell(int data, bool alignRight)
{
    if (alignRight)
    {
        return $"{data,9:N2}";
    }
    else
    {
        return $"{data,-9:N2}";
    }
}

Then when we call them:

Console.WriteLine(BasicStrings.ThisManyBottlesOfBeerOnTheWall(99));
Console.WriteLine(BasicStrings.DisplayTableCell(150, true));

The console will show this output:

99 bottles of beer on the wall, 99 bottles of beer.
   150,00

We see the default (99) and specific (150,00) integral number format, as well as column padding (notice the three spaces before 150,00).

Newlines in Interpolation Expression

Since the expression must be a valid C# expression, starting from C#11 it can contain newlines. If we still don’t have C# 11 we can still try this if we include preview features by modifying the project file:

<LangVersion>preview</LangVersion>

Then we can add a new method to the BasicStrings class:

public static string NewLinesInExpression(int place)
{
    return $"You took {place}{place switch
    {
        1 => "st",
        2 => "nd",
        3 => "rd",
        _ => "th",
    }} place.";
}

Once we use this method:

Console.WriteLine(BasicStrings.NewLinesInExpression(1));
Console.WriteLine(BasicStrings.NewLinesInExpression(2));
Console.WriteLine(BasicStrings.NewLinesInExpression(3));
Console.WriteLine(BasicStrings.NewLinesInExpression(4));

We can inspect the output:

You took 1st place.
You took 2nd place.
You took 3rd place.
You took 4th place.

As we can see, if the interpolation expression is too complex or very long it adversely affects readability which is the prime reason why we use this method. With that in mind, we should use this possibility with caution.

Escaping Special Characters

Sometimes, when constructing a character string, we want to use characters that have special meanings.

With string interpolation, special characters are

  • {, } which mark the beginning and the end of the expression that will be converted to a string and inserted in that position of the string
  • and : which separates the format string that will be applied when converting the expression to a string

To include curly braces in the interpolated string just double them ({{ and }}). To use the colon in the interpolation expression we can enclose it in parentheses:

public static class EscapedStrings
{
    public static string SimpleEscapedCurlyBraces()
    {
        const string item = "curly braces";

        return $"We show {{{item}}} inserted in the string.";
    }

    public static string ThisManyBottlesOfBeerOnTheWall(int bottles)
    {
        return $"{bottles} bottle{(bottles == 1 ? string.Empty : "s")} of beer on the wall.";
    }
}

When we use these methods:

Console.WriteLine(EscapedStrings.SimpleEscapedCurlyBraces());

Console.WriteLine(EscapedStrings.ThisManyBottlesOfBeerOnTheWall(2));
Console.WriteLine(EscapedStrings.ThisManyBottlesOfBeerOnTheWall(1));

The output will be:

We show {curly braces} inserted in the string.
2 bottles of beer on the wall.
1 bottle of beer on the wall.

Combining With Verbatim and Raw Strings

We can also use string interpolation with verbatim and raw strings in the same scenarios where they would be helpful usually.

Verbatim Strings

Verbatim strings are very useful if we are working with paths or other strings that may contain \ newlines. Given that verbatim strings are identified by @ immediately before ", we can use any order of @ and $ – @$ and [email protected]. Let’s see this in practice.

Let’s define a VerbatimAndRawStrings class and two methods:

public static string SimpleVerbatimString(string imageName)
{
    return @$"C:\images\{imageName}";
}

public static string AnotherVerbatimString(string imageName)
{
    return [email protected]"C:\images\{imageName}";
}

When we call these methods:

Console.WriteLine(VerbatimAndRawStrings.SimpleVerbatimString("profilePhoto.jpg"));
Console.WriteLine(VerbatimAndRawStrings.AnotherVerbatimString("profilePhoto.jpg"));

We can see the result in the console window:

C:\images\profilePhoto.jpg
C:\images\profilePhoto.jpg

Raw Strings

Starting with C# 11 we can also use raw string denoted by opening and closing with three or more ". This feature is particularly useful with multi-line strings that we indent to fit the surrounding code. Unlike with verbatim strings, the indentation will not end up in the resulting string. For more info check out Raw String Literals.

To see this in practice, let’s add another method to our VerbatimAndRawStrings class:

public static string SimpleRawString()
{
    const string canWe = "can";

    return $"""In raw string we {canWe} use " and line break without escaping.""";
}

After we call this method, we can inspect the result:

In raw string we can use " and line break without escaping.

If we also need to use curly braces in the text, we can choose how many braces will be treated as literal by the number of $ in front of the string. The exact number denotes the start of the interpolation expression, and any less will show up in the output. If we have more { or } it will be evaluated inside-out.

Let’s try it out with another method in our example class:

public static string EscapedRawString()
{
    const string howMany = "the same number";
    const string canWe = "can";

    return $$"""In raw string we use {{howMany}} of {} as $ that prefix the raw string. We {{{canWe}}} also enclose expression.""";
}

When we print this out to the console the output will be:

In raw string we use the same number of {} as $ that prefix the raw string. We {can} also enclose expression.

Keep in mind that if we use .NET 6, we have to use <LangVersion>preview</LangVersion> in the project file.

Working With Cultures

When we use the interpolated string directly or assign it to a string variable, it uses the CurrentCulture to format the expressions. If however, we need to use specific culture or format we have two more options. 

One option is to assign it to a IFormattable variable. We can now use this instance to create multiple strings with a specific format. You can find more information about using an IFormattable interface here.

Another option is to assign it to a FormattableString variable. From this instance, we can also create multiple strings with a current, invariant, or specific culture. For more details see FormattableString Class and Custom formatting with ICustomFormatter

Let’s try this out as an example:

DateTime date = new(2022, 11, 12, 13, 14, 15, DateTimeKind.Utc);

IFormattable message = $"Date is: {date}";
Console.WriteLine(message.ToString(null, CultureInfo.GetCultureInfo("en-US")));
Console.WriteLine(message.ToString(null, CultureInfo.GetCultureInfo("sv-SE")));

FormattableString anotherMessage = $"Due date is: {date:D}";
Console.WriteLine(anotherMessage.ToString(CultureInfo.GetCultureInfo("en-US")));
Console.WriteLine(anotherMessage.ToString(CultureInfo.GetCultureInfo("sv-SE")));
Console.WriteLine(FormattableString.Invariant(anotherMessage));
Console.WriteLine(FormattableString.CurrentCulture(anotherMessage));

Even though the IFormattable interface method ToString accepts a format string argument, it will not apply it to interpolated expressions. However, culture can be specified. If we need to specify a format string, we should do it in interpolated expression. FormattableString offers a better method that accepts only IFormatProvider.

Behind the Scenes

If you’ve come this far you are probably bugged by a strange feeling that string interpolation is just string.Format with a fancy outfit, and you’d be right to think so. If we use string interpolation directly or assign it to a string variable, the compiler will, in its place, generate a call to string.Format. In some occasions, the compiler may determine that it is better implemented as a concatenation so that it will generate a call to string.Concat instead.

However, if we use it as IFormattable or FormattableString, the compiler will generate a call to FormattableStringFactory.Create, which is very similar to string.Format.

Conclusion

We’ve come a long way since the beginning. We’ve learned how we can use string interpolation to construct strings from variables in a very readable and consistent way using string interpolation. We saw that this feature is just syntactic sugar, but a very useful one. It helps us be more confident when constructing strings, which are an essential part of any modern computer program.