Stats

Contributors: 91 Thursday, June 29, 2017
Licensed under: CC-BY-SA
Not affiliated with Stack Overflow
Rip Tutorial: riptutorial@gmail.com
Roadmap: roadmap

String interpolation

Download c# eBook

Example

String interpolation allows the developer to combine variables and text to form a string.


Basic Example

Two int variables are created: foo and bar.

int foo = 34;
int bar = 42;

string resultString = $"The foo is {foo}, and the bar is {bar}.";

Console.WriteLine(resultString);

Output:

The foo is 34, and the bar is 42.

View Demo

Braces within strings can still be used, like this:

var foo = 34;
var bar = 42;

// String interpolation notation (new style)
Console.WriteLine($"The foo is {{foo}}, and the bar is {{bar}}.");

This produces the following output:

The foo is {foo}, and the bar is {bar}.


Using interpolation with verbatim string literals

Using @ before the string will cause the string to be interpreted verbatim. So, e.g. Unicode characters or line breaks will stay exactly as they've been typed. However, this will not effect the expressions in an interpolated string as shown in the following example:

Console.WriteLine($@"In case it wasn't clear:
\u00B9
The foo
is {foo},
and the bar
is {bar}.");
Output:

In case it wasn't clear:
\u00B9
The foo
is 34,
and the bar
is 42.

View Demo


Expressions

With string interpolation, expressions within curly braces {} can also be evaluated. The result will be inserted at the corresponding location within the string. For example, to calculate the maximum of foo and bar and insert it, use Math.Max within the curly braces:

Console.WriteLine($"And the greater one is: { Math.Max(foo, bar) }");

Output:

And the greater one is: 42

Note: Any leading or trailing whitespace (including space, tab and CRLF/newline) between the curly brace and the expression is completely ignored and not included in the output

View Demo

As another example, variables can be formatted as a currency:

Console.WriteLine($"Foo formatted as a currency to 4 decimal places: {foo:c4}");

Output:

Foo formatted as a currency to 4 decimal places: $34.0000

View Demo

Or they can be formatted as dates:

Console.WriteLine($"Today is: {DateTime.Today:dddd, MMMM dd - yyyy}");

Output:

Today is: Monday, July, 20 - 2015

View Demo

Statements with a Conditional (Ternary) Operator can also be evaluated within the interpolation. However, these must be wrapped in parentheses, since the colon is otherwise used to indicate formatting as shown above:

Console.WriteLine($"{(foo > bar ? "Foo is larger than bar!" : "Bar is larger than foo!")}");

Output:

Bar is larger than foo!

View Demo

Conditional expressions and format specifiers can be mixed:

Console.WriteLine($"Environment: {(Environment.Is64BitProcess ? 64 : 32):00'-bit'} process");

Output:

Environment: 32-bit process


Escape sequences

Escaping backslash (\) and quote (") characters works exactly the same in interpolated strings as in non-interpolated strings, for both verbatim and non-verbatim string literals:

Console.WriteLine($"Foo is: {foo}. In a non-verbatim string, we need to escape \" and \\ with backslashes.");
Console.WriteLine($@"Foo is: {foo}. In a verbatim string, we need to escape "" with an extra quote, but we don't need to escape \");

Output:

Foo is 34. In a non-verbatim string, we need to escape " and \ with backslashes.
Foo is 34. In a verbatim string, we need to escape " with an extra quote, but we don't need to escape \

To include a curly brace { or } in an interpolated string, use two curly braces {{ or }}:

$"{{foo}} is: {foo}"

Output:

{foo} is: 34

View Demo


FormattableString type

The type of a $"..." string interpolation expression is not always a simple string. The compiler decides which type to assign depending on the context:

string s = $"hello, {name}";
System.FormattableString s = $"Hello, {name}";
System.IFormattable s = $"Hello, {name}";

This is also the order of type preference when the compiler needs to choose which overloaded method is going to be called.

A new type, System.FormattableString, represents a composite format string, along with the arguments to be formatted. Use this to write applications that handle the interpolation arguments specifically:

public void AddLogItem(FormattableString formattableString)
{
    foreach (var arg in formattableString.GetArguments())
    {
        // do something to interpolation argument 'arg'
    }

    // use the standard interpolation and the current culture info
    // to get an ordinary String:
    var formatted = formattableString.ToString();

    // ...
}

Call the above method with:

AddLogItem($"The foo is {foo}, and the bar is {bar}.");
For example, one could choose not to incur the performance cost of formatting the string if the logging level was already going to filter out the log item.

Implicit conversions

There are implicit type conversions from an interpolated string:

var s = $"Foo: {foo}";
System.IFormattable s = $"Foo: {foo}";
You can also produce an IFormattable variable that allows you to convert the string with invariant context:
var s = $"Bar: {bar}";
System.FormattableString s = $"Bar: {bar}";

Current and Invariant Culture Methods

If code analysis is turned on, interpolated strings will all produce warning CA1305 (Specify IFormatProvider). A static method may be used to apply current culture.

public static class Culture
{
    public static string Current(FormattableString formattableString)
    {
        return formattableString?.ToString(CultureInfo.CurrentCulture);
    }
    public static string Invariant(FormattableString formattableString)
    {
        return formattableString?.ToString(CultureInfo.InvariantCulture);
    }
}

Then, to produce a correct string for the current culture, just use the expression:

Culture.Current($"interpolated {typeof(string).Name} string.")
Culture.Invariant($"interpolated {typeof(string).Name} string.")
Note: Current and Invariant cannot be created as extension methods because, by default, the compiler assigns type String to interpolated string expression which causes the following code to fail to compile:

$"interpolated {typeof(string).Name} string.".Current();

FormattableString class already contains Invariant() method, so the simplest way of switching to invariant culture is by relying on using static:

using static System.FormattableString;

string invariant = Invariant($"Now = {DateTime.Now}"); string current = $"Now = {DateTime.Now}";


Behind the scenes

Interpolated strings are just a syntactic sugar for String.Format(). The compiler (Roslyn) will turn it into a String.Format behind the scenes:

var text = $"Hello {name + lastName}";

The above will be converted to something like this:

string text = string.Format("Hello {0}", new object[] {
    name + lastName
});

String Interpolation and Linq

It's possible to use interpolated strings in Linq statements to increase readability further.

var fooBar = (from DataRow x in fooBarTable.Rows
          select string.Format("{0}{1}", x["foo"], x["bar"])).ToList();

Can be re-written as:

var fooBar = (from DataRow x in fooBarTable.Rows
          select $"{x["foo"]}{x["bar"]}").ToList();

Reusable Interpolated Strings

With string.Format, you can create reusable format strings:

public const string ErrorFormat = "Exception caught:\r\n{0}";

// ...

Logger.Log(string.Format(ErrorFormat, ex));

Interpolated strings, however, will not compile with placeholders referring to non-existent variables. The following will not compile:

public const string ErrorFormat = $"Exception caught:\r\n{error}";
// CS0103: The name 'error' does not exist in the current context

Instead, create a Func<> which consumes variables and returns a String:

public static Func<Exception, string> FormatError =
    error => $"Exception caught:\r\n{error}";

// ...

Logger.Log(FormatError(ex));

String interpolation and localization

If you’re localizing your application you may wonder if it is possible to use string interpolation along with localization. Indeed, it would be nice to have the possibility to store in resource files Strings like:

"My name is {name} {middlename} {surname}"
instead of the much less readable:

"My name is {0} {1} {2}"

String interpolation process occurs at compile time, unlike formatting string with string.Format which occurs at runtime. Expressions in an interpolated string must reference names in the current context and need to be stored in resource files. That means that if you want to use localization you have to do it like:

var FirstName = "John";

// method using different resource file "strings"
// for French ("strings.fr.resx"), German ("strings.de.resx"), 
// and English ("strings.en.resx")
void ShowMyNameLocalized(string name, string middlename = "", string surname = "")
{
    // get localized string
    var localizedMyNameIs = Properties.strings.Hello;
    // insert spaces where necessary
    name = (string.IsNullOrWhiteSpace(name) ? "" : name + " ");
    middlename = (string.IsNullOrWhiteSpace(middlename) ? "" : middlename + " ");
    surname = (string.IsNullOrWhiteSpace(surname) ? "" : surname + " ");
    // display it
    Console.WriteLine($"{localizedMyNameIs} {name}{middlename}{surname}".Trim());
}

// switch to French and greet John
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("fr-FR");
ShowMyNameLocalized(FirstName);

// switch to German and greet John
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("de-DE");
ShowMyNameLocalized(FirstName);

// switch to US English and greet John
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("en-US");
ShowMyNameLocalized(FirstName);

If the resource strings for the languages used above are correctly stored in the individual resource files, you should get the following output:

Bonjour, mon nom est John
Hallo, mein Name ist John
Hello, my name is John

Note that this implies that the name follows the localized string in every language. If that is not the case, you need to add placeholders to the resource strings and modify the function above or you need to query the culture info in the function and provide a switch case statement containing the different cases. For more details about resource files, see How to use localization in C#.

It is a good practice to use a default fallback language most people will understand, in case a translation is not available. I suggest to use English as default fallback language.

Recursive interpolation

Although not very useful, it is allowed to use an interpolated string recursively inside another's curly brackets:

Console.WriteLine($"String has {$"My class is called {nameof(MyClass)}.".Length} chars:");
Console.WriteLine($"My class is called {nameof(MyClass)}.");

Output:

String has 27 chars:

My class is called MyClass.