String interpolation allows the developer to combine variables
and text to form a string.
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.
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 @
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.
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
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
Or they can be formatted as dates:
Console.WriteLine($"Today is: {DateTime.Today:dddd, MMMM dd - yyyy}");
Output:
Today is: Monday, July, 20 - 2015
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!
Conditional expressions and format specifiers can be mixed:
Console.WriteLine($"Environment: {(Environment.Is64BitProcess ? 64 : 32):00'-bit'} process");
Output:
Environment: 32-bit process
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
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.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}";
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}";
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
});
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();
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));
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 String
s 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.
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.