原始字符串字面量

Tip

本文是已了解至少一种编程语言并正在学习 C# 的开发人员的 “基础知识 ”部分的一部分。 如果你不熟悉编程,请先 学习入门 教程。 有关完整的语法,请参阅 语言参考

来自另一种语言? C# 原始字符串字面量起到与 Python 和 Rust 的 r"..." 字符串、Java 的文本块("""..."""),以及 JavaScript、TypeScript 和 Go 中的反引号模板字符串相同的作用。 C# 的语法与 Java 的文本块最为接近,但额外增加了针对可变长度分隔符和插值的规则。

原始字符串文本由三个或多个双引号分隔。 在分隔符内,每个字符都会按字面意义处理。 引号和反斜杠不需要转义,换行符将保留为写入。 对包含引号、反斜杠或多行的任何字符串使用原始字符串:JSON、XML、SQL、正则表达式、文件路径和代码示例。

Warning

原始字符串文本使 SQL 更易于读取,但它不会使 SQL 更安全。 切勿将用户提供的值连接或内插到 SQL 命令中。 这种做法会使你的应用程序容易受到 SQL 注入攻击。 请改用参数化命令:DbCommand.CreateParameterDbParameterCollection.Add,或 Entity Framework CoreDapper 中的更高级别帮助程序。 同样警告适用于其他容易注入的格式,例如 shell 命令、LDAP 筛选器和 HTML。

包含引号和反斜杠的文本

常规字面量需要对 "\ 进行转义。 逐字文本仍然需要 "" 嵌入引号。 原始文本既不需要:

// Same JSON value, three ways:
string regular  = "{ \"name\": \"Ada\", \"path\": \"C:\\\\src\" }";
string verbatim = @"{ ""name"": ""Ada"", ""path"": ""C:\\src"" }";
string raw      = """{ "name": "Ada", "path": "C:\\src" }""";

Console.WriteLine(regular  == raw);   // True
Console.WriteLine(verbatim == raw);   // True

每个窗体生成相同的字符串,但原始版本读取的字符串与它所表示的 JSON 完全相同。

单行原始字符串

起始分隔符和结束分隔符各自都至少由三个双引号组成,且结束分隔符所用的引号数量必须与起始分隔符相同。 内容位于它们之间,并且在同一行上。 内容中的引号和反斜杠都是字面字符:

// A raw string literal starts and ends with at least three quotes.
// Inside, " and \ are literal — no escaping required.
string message = """She said "hi" and left.""";
string regex   = """\d{3}-\d{4}""";

Console.WriteLine(message);   // She said "hi" and left.
Console.WriteLine(regex);     // \d{3}-\d{4}

单行原始字符串在其分隔符之间不能为空。 它可以以双引号结尾,但不能以双引号开头。 编译器将前导双引号视为另一个开始分隔符字符。 如果内容必须以引号开头,请改用多行原始字符串字面量,这样内容会位于单独的一行,开头的引号也就不会产生歧义。

多行原始字符串

对于多行内容,起始分隔符位于行尾,结束分隔符单独位于下一行的行首。 与单行原始字符串一样,分隔符为三个或多个双引号,结束分隔符必须使用与左引号相同的引号数。 三个引号是常见情况,但如果内容本身包含连续的一串 """,则可以使用四个、五个或更多引号。 两个分隔符之间的一切都是字符串的值,完全如所写:

// The opening """ and closing """ each sit on their own line.
// The content between them is the value, exactly as written.
string sql = """
    SELECT id, name
    FROM customers
    WHERE active = 1
    """;

Console.WriteLine(sql);

紧跟在起始 """ 之后的换行符以及紧邻结束 """ 之前的换行符都不是该值的一部分。 它们是分隔空白字符。 同样,编译器会从每个内容行的右下 """ 角处去除任何空格,以便可以缩进文本以匹配其封闭代码块,而不会在字符串中显示该缩进。 下一部分详细介绍了此规则。

如果内容本身包含一串 """,请使用四个或更多引号作为定界符。 分隔符的数量只需超过内容中连续引号的最长序列。 有关完整规则 ,请参阅原始字符串文本(语言参考 )。

缩进:闭合定界符决定缩进边界

闭合 """ 的列定义了左边距。 编译器会从每一行内容中删除直到该列位置为止的空白字符。 此规则允许你缩进文本以匹配周围的代码,而不会污染值:

// The column of the closing """ sets a left margin.
// Whitespace up to that column is stripped from every content line.
string xml = """
        <order id="42">
            <item>book</item>
        </order>
        """;

// First content line begins at column 0 of the value:
Console.WriteLine(xml);
// <order id="42">
//     <item>book</item>

如果内容行的前导空格字符数少于结束分隔符的列,编译器将报告错误。 使所有内容行至少缩进到结束 """

原始内插字符串

$ 前缀添加到原始字符串以启用内插。 对 {} 孔位中的表达式进行求值,并将其结果插入到该值中:

// A single $ before """ enables interpolation: single { and } mark a hole.
// Inside a single-$ raw string, literal braces aren't allowed — use $$ when
// the content also contains literal { or }.
string name = "Ada";
int    score = 95;

string report = $"""
    Player:  {name}
    Score:   {score}
    Updated: {DateTime.UtcNow:yyyy-MM-dd}
    """;

Console.WriteLine(report);

如果内插内容也需要文本{}字符,请参阅原始字符串文本(语言参考)。

何时选择哪个文本

每当内容包含引号、反斜杠或多行时,使用 原始字符串文本 。 这样一来,内容读起来更简短,复制粘贴更方便,也不会出现转义序列问题。

对短单行值使用 常规字符串文本 ,而不使用引号或反斜杠,例如名称、消息、格式占位符。

仅在处理使用此类写法的现有代码时,才使用 逐字字符串字面量@"...")。 对于新代码,原始字符串涵盖了逐字字符串适用的所有情况,并且在嵌入引号时语法更简洁。

另见