使用 $ 的字符串内插

$ 字符将字符串字面量标识为内插字符串。 内插字符串是可能包含内插表达式的字符串文本 。 将内插字符串解析为结果字符串时,带有内插表达式的项会替换为表达式结果的字符串表示形式。

字符串内插为格式化字符串提供了一种可读性和便捷性更高的方式。 它比字符串复合格式设置更容易阅读。 下面的示例使用了这两种功能生成同样的输出结果:

var name = "Mark";
var date = DateTime.Now;

// Composite formatting:
Console.WriteLine("Hello, {0}! Today is {1}, it's {2:HH:mm} now.", name, date.DayOfWeek, date);
// String interpolation:
Console.WriteLine($"Hello, {name}! Today is {date.DayOfWeek}, it's {date:HH:mm} now.");
// Both calls produce the same output that is similar to:
// Hello, Mark! Today is Wednesday, it's 19:40 now.

从 C# 10 开始,可以使用内插字符串来初始化常量字符串。 仅当内插字符串中的所有内插表达式也是常量字符串时,才可以执行此操作。

内插字符串的结构

若要将字符串标识为内插字符串,可在该字符串前面加上 $ 符号。 字符串字面量开头的 $" 之间不能有任何空格。

具备内插表达式的项的结构如下所示:

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

括号中的元素是可选的。 下表说明了每个元素:

元素 描述
interpolationExpression 生成需要设置格式的结果的表达式。 null 的字符串表示形式为 String.Empty
alignment 常数表达式,它的值定义表达式结果的字符串表示形式中的最小字符数。 如果值为正,则字符串表示形式为右对齐;如果值为负,则为左对齐。 有关详细信息,请参阅复合格式设置一文的对齐组件部分。
formatString 受表达式结果类型支持的格式字符串。 有关详细信息,请参阅复合格式设置一文的格式字符串组件部分。

以下示例使用上述可选的格式设置组件:

Console.WriteLine($"|{"Left",-7}|{"Right",7}|");

const int FieldWidthRightAligned = 20;
Console.WriteLine($"{Math.PI,FieldWidthRightAligned} - default formatting of the pi number");
Console.WriteLine($"{Math.PI,FieldWidthRightAligned:F3} - display only three decimal digits of the pi number");
// Output is:
// |Left   |  Right|
//     3.14159265358979 - default formatting of the pi number
//                3.142 - display only three decimal digits of the pi number

从 C# 11 开始,可以在内插表达式中使用换行符,以使表达式的代码更具可读性。 下面的示例展示了换行符如何提高涉及模式匹配的表达式的可读性:

string message = $"The usage policy for {safetyScore} is {
    safetyScore switch
    {
        > 90 => "Unlimited usage",
        > 80 => "General usage, with daily safety check",
        > 70 => "Issues must be addressed within 1 week",
        > 50 => "Issues must be addressed within 1 day",
        _ => "Issues must be addressed before continued use",
    }
    }";

内插原始字符串字面量

从 C# 11 开始,可以使用内插原始字符串字面量,如以下示例所示:

int X = 2;
int Y = 3;

var pointMessage = $"""The point "{X}, {Y}" is {Math.Sqrt(X * X + Y * Y):F3} from the origin""";

Console.WriteLine(pointMessage);
// Output is:
// The point "2, 3" is 3.606 from the origin

要在结果字符串中嵌入 {} 字符,请让内插原始字符串字面量以多个 $ 字符开头。 执行此操作时,任何长度短于 $ 字符数的 {} 字符序列都会嵌入到结果字符串中。 若要将任何内插表达式包含在该字符串中,需要使用与 $ 字符数相同的大括号数,如以下示例所示:

int X = 2;
int Y = 3;

var pointMessage = $$"""{The point {{{X}}, {{Y}}} is {{Math.Sqrt(X * X + Y * Y):F3}} from the origin}""";
Console.WriteLine(pointMessage);
// Output is:
// {The point {2, 3} is 3.606 from the origin}

在前面的示例中,内插原始字符串字面量以两个 $ 字符开头。 这就是为什么你需要将每个内插表达式放在双大括号 {{}} 之间。 单个大括号嵌入到结果字符串中。 如果需要将重复的 {} 字符嵌入结果字符串中,请使用相应增加的 $ 字符数来指定内插原始字符串字面量。

特殊字符

要在内插字符串生成的文本中包含大括号 "{" 或 "}",请使用两个大括号,即 "{{" 或 "}}"。 有关详细信息,请参阅复合格式设置一文的转义括号部分。

因为冒号(“:”)在内插表达式项中具有特殊含义,为了在内插表达式中使用条件运算符,请将表达式放在括号内。

以下示例演示了如何在结果字符串中包括大括号。 它还演示了如何使用条件运算符:

string name = "Horace";
int age = 34;
Console.WriteLine($"He asked, \"Is your name {name}?\", but didn't wait for a reply :-{{");
Console.WriteLine($"{name} is {age} year{(age == 1 ? "" : "s")} old.");
// Output is:
// He asked, "Is your name Horace?", but didn't wait for a reply :-{
// Horace is 34 years old.

内插逐字字符串$@ 字符开头。 可以按任意顺序使用 $@$@"..."@$"..." 均为有效的内插逐字字符串。 有关逐字字符串的详细信息,请参阅字符串逐字标识符文章。

特定于区域性的格式设置

默认情况下,内插字符串将 CultureInfo.CurrentCulture 属性定义的当前区域性用于所有格式设置操作。

要将内插字符串解析为特定于区域性的结果字符串,请使用 String.Create(IFormatProvider, DefaultInterpolatedStringHandler) 方法,该方法从 .NET 6 开始可用。 下面的示例演示如何执行此操作:

double speedOfLight = 299792.458;

System.Globalization.CultureInfo.CurrentCulture = System.Globalization.CultureInfo.GetCultureInfo("nl-NL");
string messageInCurrentCulture = $"The speed of light is {speedOfLight:N3} km/s.";

var specificCulture = System.Globalization.CultureInfo.GetCultureInfo("en-IN");
string messageInSpecificCulture = string.Create(
    specificCulture, $"The speed of light is {speedOfLight:N3} km/s.");

string messageInInvariantCulture = string.Create(
    System.Globalization.CultureInfo.InvariantCulture, $"The speed of light is {speedOfLight:N3} km/s.");

Console.WriteLine($"{System.Globalization.CultureInfo.CurrentCulture,-10} {messageInCurrentCulture}");
Console.WriteLine($"{specificCulture,-10} {messageInSpecificCulture}");
Console.WriteLine($"{"Invariant",-10} {messageInInvariantCulture}");
// Output is:
// nl-NL      The speed of light is 299.792,458 km/s.
// en-IN      The speed of light is 2,99,792.458 km/s.
// Invariant  The speed of light is 299,792.458 km/s.

在 .NET 5 和更早的 .NET 版本中,请使用从内插字符串到 FormattableString 实例的隐式转换。 然后,可以使用实例 FormattableString.ToString(IFormatProvider) 方法或静态 FormattableString.Invariant 方法来生成特定于区域性的结果字符串。 下面的示例演示如何执行此操作:

double speedOfLight = 299792.458;
FormattableString message = $"The speed of light is {speedOfLight:N3} km/s.";

var specificCulture = System.Globalization.CultureInfo.GetCultureInfo("en-IN");
string messageInSpecificCulture = message.ToString(specificCulture);
Console.WriteLine(messageInSpecificCulture);
// Output:
// The speed of light is 2,99,792.458 km/s.

string messageInInvariantCulture = FormattableString.Invariant(message);
Console.WriteLine(messageInInvariantCulture);
// Output is:
// The speed of light is 299,792.458 km/s.

有关自定义格式设置的详细信息,请参阅在 .NET 中设置类型格式一文中的使用 ICustomFormatter 进行自定义格式设置部分。

其他资源

如果你不熟悉字符串内插,请参阅 C# 中的字符串内插交互式教程。 还可查看另一个 C# 中的字符串内插教程。 该教程演示了如何使用内插字符串生成带格式的字符串。

内插字符串编译

从 C# 10 和 .NET 6 开始,编译器会检查内插字符串是否被分配给满足内插字符串处理程序模式要求的类型。 内插字符串处理程序是一种将内插字符串转换为结果字符串的类型。 当内插字符串的类型为 string 时,它由 System.Runtime.CompilerServices.DefaultInterpolatedStringHandler 处理。 有关自定义内插字符串处理程序的示例,请参阅编写自定义字符串内插处理程序教程。 使用内插字符串处理程序是一种高级方案,通常出于性能原因而需要使用。

注意

内插字符串处理程序的一个副作用是,自定义处理程序(包括 System.Runtime.CompilerServices.DefaultInterpolatedStringHandler)可能不会在所有条件下都计算内插字符串中的所有内插表达式。 这意味着这些表达式的副作用可能不会发生。

在 C# 10 之前,如果内插字符串类型为 string,则通常将其转换为 String.Format 方法调用。 如果分析的行为等同于串联,则编译器可将 String.Format 替换为 String.Concat

如果内插字符串类型为 IFormattableFormattableString,则编译器会生成对 FormattableStringFactory.Create 方法的调用。

C# 语言规范

有关详细信息,请参阅 C# 语言规范内插字符串表达式部分以及以下新功能规范:

另请参阅