NDepend Blog

Improve your .NET code quality with NDepend

C# String Interpolation Explained

May 25, 2026 9 minutes read

C# String Interpolation Explained

C# string interpolation is the dollar-sign syntax that embeds variables and expressions directly inside a string literal. Introduced in C# 6, it has largely replaced String.Format and string concatenation in idiomatic C# code.

It has also kept evolving. C# 10 and C# 11 made it both more capable and surprisingly efficient under the hood. This post covers the full syntax, the performance story, when interpolation is the wrong tool, and the questions developers ask most.

What is C# String Interpolation?

String interpolation in C# is a compiler feature that builds a string by evaluating expressions placed inside curly braces within a string literal prefixed with a dollar sign. It was added in C# 6.0 (2015) as a more readable alternative to String.Format("{0} {1}", a, b), and over time it has also become the most efficient way to format a string in .NET. If an expression evaluates to null, the hole simply produces an empty string rather than throwing.

The minimal example:

This program outputs: Hello, my name is John and I am 30 years old.

Visual Studio, VS Code and JetBrains Rider all highlight the interpolated holes inside the editor, which makes long format strings noticeably easier to scan:

Visual Studio highlights C# string interpolation holes in the editor

The Anatomy of an Interpolated String

Almost everything in the rest of this article is a variation on a single grammar. Every interpolation hole has one mandatory part and two optional ones, always in this order:

  • expression – any C# expression that returns a value. A null result becomes the empty string.
  • alignment – a signed integer minimum field width. Positive right-aligns, negative left-aligns.
  • formatString – any standard or custom format the value’s type already understands (the same vocabulary as ToString()).

Keep those three slots straight and the alignment, formatting and culture sections below all fall into place.

Embedding Expressions, Not Just Variables

What goes between the braces of an interpolated string is a full C# expression, not just an identifier. You can call methods, do arithmetic, index into an array, or compose anything that produces a value:

This program outputs: Total cost: 32.3892

Conditional (Ternary) Expressions Inside an Interpolated String

The ternary operator works fine inside a hole, but it has to be wrapped in parentheses – otherwise the compiler reads the first ? as the optional-format separator and bails out with a cryptic error. This is by far the most common mistake I see when developers reach for branching inside an interpolated string:

Formatting Numbers, Currencies and Dates

After an expression, the colon : introduces a standard or custom .NET format specifier – the same vocabulary used by ToString() and String.Format.

A custom numeric format:

Output: Pi to three decimal places: 3.142

A standard currency format:

Output: Total cost: $32.29 (under an en-US culture).

A custom date format:

Output: Current date: 2026-May-14

The format specifiers you reach for most often are worth memorizing:

  • {x:C} – currency, e.g. $1,234.50
  • {x:N2} – number with thousands separators and 2 decimals, e.g. 1,234.50
  • {x:F2} – fixed-point with 2 decimals, no separators, e.g. 1234.50
  • {x:P1} – percent with 1 decimal, e.g. 12.3 %
  • {x:X} – hexadecimal for integers, e.g. FF
  • {d:yyyy-MM-dd} – ISO-style date, e.g. 2026-05-25

One thing worth remembering: interpolation uses the current thread’s culture. If the result will be parsed later – a JSON number, a file path, a CSV cell – use FormattableString.Invariant($"...") instead of relying on the default. Cross-locale bugs caused by a stray comma decimal separator are a classic gotcha (more on that below).

Aligning Values with the Comma Syntax

A comma , after the expression specifies the minimum field width. A positive number right-aligns, a negative number left-aligns:

This program outputs:

Beware: if the value is wider than the field, the runtime does not truncate it. One oversized value silently breaks the alignment of every following row, which is why ad-hoc tables built this way always end up looking ragged in production logs. For real tabular output, validate widths yourself or use a dedicated table-formatting library.

You can combine alignment and format in the same hole: {value,10:N2} reserves ten characters and formats with two decimal places.

New Lines Inside an Interpolated String (C# 11)

Since C# 11, you can put line breaks inside an interpolation hole. The rule applies to the expression, not the literal portion, but it makes interpolated strings that contain a pattern match or a multi-line LINQ query dramatically more readable:

To break the literal text across lines you still need a verbatim ($@"...") or raw ($"""...""") string.

Escaping Curly Braces (and Raw String Interpolation)

To include a literal curly brace in the output, double the character:

Output: {This message includes 3 curly braces}.

This doubling rule is inherited from String.Format and quickly becomes painful when the string you’re building is itself JSON, XML, or C# source code full of braces. C# 11 fixed that with raw string literals: prefix with $$ instead of $ and the compiler treats only double braces as interpolation holes. Single braces stay literal – no escaping required:

This program outputs:

Console output of a C# 11 raw string literal with $$ interpolation producing JSON

The number of $ signs you can stack is unbounded. Three dollar signs means triple braces are the interpolation markers, and so on. This lets an interpolated string contain any sequence of consecutive braces verbatim:

C# 11 raw literal strings combined with multi-dollar interpolation strings

Interpolated Strings as Constants (C# 10)

A small but handy C# 10 addition: if every hole in an interpolated string is itself a constant, the whole thing can be a const string. This lets you compose readable constants without dropping back to concatenation:

Before C# 10 this was a compile error, and only plain string literals could be constants. The expressions still have to be compile-time constants – a method call or a non-const variable in any hole pushes the result back to a regular runtime string.

Controlling Culture: Invariant vs Culture-Specific Output

By default an interpolated string formats every value with CultureInfo.CurrentCulture. That is exactly what you want for text shown to a user, and exactly what you do not want for machine-readable output, because the decimal separator, date order and digit grouping all change with the locale.

Since .NET 6 the cleanest way to pin the culture is the string.Create(IFormatProvider, ...) overload, which accepts the interpolated string directly:

For the very common “I just need invariant” case, FormattableString.Invariant($"...") is the shorter form and works on every framework since C# 6. Reach for one of these any time the string ends up in JSON, a URL, a SQL literal, a file name or a log that another program will parse.

How C# String Interpolation Compiles: the Performance Story

Before C# 10 and .NET 6, an interpolated string was syntactic sugar over String.Format. Concretely, this program:

…compiled into this:

That meant the format string was re-parsed at runtime on every call, the arguments were boxed into an object[], and an intermediate StringBuilder was allocated. Fine for an occasional log line, expensive in a hot path.

Since C# 10 and .NET 6, the same interpolated string compiles to something quite different:

The format string is now parsed once, at build time. There is no object[], value types are appended through a generic AppendFormatted<T> overload that avoids boxing, and the working buffer is rented from an array pool instead of being freshly allocated. In Stephen Toub’s announcement the rewrite measured roughly a 40% throughput improvement and almost a 5x reduction in allocations against the old String.Format path – which is why “interpolation is slower” is now outdated advice.

ISpanFormattable: Eliminating the Last Temporary Allocations

One allocation the new handler couldn’t yet remove was the intermediate string each value produced when ToString() was called on it. Take this small type:

Until recently, formatting that Point via interpolation allocated two short-lived strings – one for X, one for Y – and copied them into the final buffer.

The ISpanFormattable interface, available since .NET 10 on the interpolation path, lets a type write its characters directly into the destination Span<char> rather than producing an intermediate string. All BCL primitive types (int, double, DateTime, Guid, …) implement it, and custom types can too. The net result is that an interpolated string made entirely of ISpanFormattable values can be produced with a single heap allocation – the final string itself.

For an in-depth coverage of the compiler interpolated string handler design, Stephen Toub’s String Interpolation in C# 10 and .NET 6 is still the canonical reference.

When You Should NOT Use C# String Interpolation

For all its convenience, interpolation is the wrong tool in two specific situations.

Building SQL or shell commands. An interpolated string concatenates the values you give it as text. Inside a SQL query that becomes a textbook SQL injection vulnerability. Always use parameterized queries, or the SQL-specific FormattableString overloads (for example the EF Core FromSqlInterpolated method, which routes the holes into parameters instead of into the SQL text).

Logging. A call like logger.LogInformation($"Processed user {userId}") performs the formatting work even when the log level is filtered out – wasted CPU and allocations on every call. Worse, the structured-logging pipeline never sees userId as a discrete field; it sees one already-formatted message. Use the {Placeholder} message-template syntax of your logger instead: logger.LogInformation("Processed user {UserId}", userId).

Outside of those two cases, reach for the dollar sign. The readability win is real, the compiler now catches typos and missing values, and as we saw above the runtime cost is essentially nil.

String Interpolation vs String.Format vs Concatenation

The same output can usually be expressed three ways in C#. Here is how they compare on the criteria that matter day to day:

Approach Readability Compile-time checking Performance on .NET 6+ Best for
Interpolation $"...{x}..." Highest Yes – a missing variable is a build error Fastest – direct handler calls, no boxing Almost everything
String.Format("{0}", x) Medium No – index mistakes surface at runtime Slower – format string parsed on every call Format strings that come from a resource file or config
Concatenation "x = " + x Low past 2-3 parts Partial Fine for a couple of short pieces Joining two or three short values

If you maintain older code from before C# 6, converting String.Format calls into interpolated strings is one of the safer modernization refactorings. Both Visual Studio and Rider offer it as a one-click code fix.

C# String Interpolation FAQ

Which C# version added string interpolation?

String interpolation shipped in C# 6.0 in 2015. Constant interpolated strings arrived in C# 10, and multi-line holes plus raw-string interpolation came with C# 11.

Is string interpolation slower than String.Format in C#?

No. Since C# 10 and .NET 6 it is generally faster, because the compiler parses the format string once at build time and emits direct calls into DefaultInterpolatedStringHandler. On older targets such as .NET Framework or netstandard2.0 it still lowers to String.Format, so the two are equivalent there.

What happens if an interpolated value is null?

Nothing breaks. A hole that evaluates to null produces the empty string, exactly as String.Format does, so you never get a NullReferenceException from interpolation itself.

How do I format a number to two decimal places?

Use the format component after a colon: $"{value:F2}" for plain fixed-point (1234.50) or $"{value:N2}" to add thousands separators (1,234.50).

Can an interpolated string be a const?

Since C# 10, an interpolated string whose holes are themselves constants is a valid const string. Before that, only plain string literals could be constants.

How do I make an interpolated string culture-invariant?

Use string.Create(CultureInfo.InvariantCulture, $"...") on .NET 6+, or the shortcut FormattableString.Invariant($"..."), or assign to a FormattableString and call .ToString(CultureInfo.InvariantCulture). This is critical for any output that will be parsed later (JSON numbers, decimal file names, machine-readable logs).

What is the difference between $@"..." and $$"""..."""?

$@"..." combines verbatim (no escape sequences, multi-line) with interpolation, and is still convenient for short Windows file paths. The $ and @ can appear in either order. $$"""...""" is the C# 11 raw-string variant; it additionally lets the content contain quotes and braces without escaping, which is what you want when the output is JSON, regex or embedded source code.

Does string interpolation protect against SQL injection?

No. $"SELECT * FROM Users WHERE Name = '{name}'" is just as dangerous as plain concatenation. Always use parameterized queries.

Can I interpolate into a StringBuilder?

Yes – since .NET 6 there is a StringBuilder.Append(ref AppendInterpolatedStringHandler) overload that lets you write sb.Append($"x = {x}") with no intermediate string allocated.

Does string interpolation work in .NET Framework?

Yes. The syntax has been available since C# 6, regardless of runtime. On .NET Framework and netstandard2.0 it compiles down to String.Format rather than to the modern handler, so you get the readability but not the .NET 6+ allocation savings.

Conclusion

C# string interpolation hits a sweet spot that very few language features manage: it is easier to read than the alternatives, the compiler catches mistakes the older APIs accepted silently, and the runtime cost has been driven down to the point where you no longer trade readability for performance. The one rule worth keeping in mind is the boundary case – never reach for the dollar sign when you’re building SQL, shell commands, or log messages that carry structured fields. Everywhere else, the modern dollar-sign syntax is now the right default in idiomatic C# code.

This article is brought to you by the team behind NDepend — a proven .NET static analysis tool for improving code maintainability, security, and overall quality. Whether you’re modernizing a legacy .NET application or starting fresh in C#, get started with your free full-featured trial today!

Leave a Reply

Your email address will not be published. Required fields are marked *