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.
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) { return alignRight ? $"{data,9:N2}" : $"{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 $@
. 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 $@"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.