C# 在处理 JSON、XML、SQL、正则和代码模板时,字符串可读性一直是个老问题:要么转义太多,要么换行和缩进难以维护。C# 11 提供的原始字符串字面量,正是为这类场景准备的。下面就按语法、边界规则、插值方式和典型应用逐段拆开,看它到底解决了什么问题,以及在实际项目里该怎么用。
什么是原始字符串字面量
从 C# 11 开始,原始字符串字面量(Raw String Literals)成为正式特性。它的核心目标很直接:让字符串内容尽可能按“原样”写进代码里,减少对特殊字符的转义处理。
这类字符串以至少三个双引号 """ 作为开始和结束分隔符,可以跨越多行,也可以直接包含引号、反斜杠等常见“麻烦字符”。对于经常要写配置片段、文档文本、查询语句或模板内容的开发者来说,它比传统写法更接近最终输出结果。
它解决了哪些老问题
不用频繁写转义符,代码阅读压力更小
天然支持多行文本,不必再靠字符串拼接维持格式
能保留文本结构,尤其适合结构化内容
可与插值语法结合,适合动态模板生成
和传统字符串相比差别在哪
| 特性 | 常规字符串 | 逐字字符串(@) | 原始字符串字面量 |
|---|---|---|---|
| 转义需求 | 需要转义特殊字符 | 不需要转义,但对双引号仍需转义 | 完全不需要转义 |
| 多行支持 | 需使用n等 | 支持 | 原生支持 |
| 缩进保留 | 不保留 | 保留所有缩进 | 智能缩进处理 |
| 引号处理 | \" | \"\" | 直接使用 |
| JSON/XML | 可读性差 | 可读性中等 | 完美呈现 |
如果字符串内容本身就接近配置文件、标记语言或查询脚本,原始字符串通常会比普通字符串和 @ 逐字字符串更自然。

基本语法怎么写
原始字符串的基本形式并不复杂,关键点是分隔符和换行位置。最少使用三个双引号,既可以写成单行,也可以写成多行。
// 基本原始字符串
string basic = """这是一个原始字符串,不需要转义 "引号" 和 反斜杠""";
// 多行原始字符串
string multiLine = """
这是一个
多行原始字符串
可以包含 "引号" 和 反斜杠
""";
// 包含大括号的原始字符串
string withBraces = """
{ "name": "John", "age": 30 }
""";
可以把它理解成“所见即所得”的字符串写法。尤其在多行场景下,文本长什么样,输出通常就接近什么样,维护起来会轻松很多。
为什么这种写法更适合多行内容
传统字符串在多行文本里往往要依赖换行符和拼接:

// 传统多行字符串(需要转义和连接)
string oldMultiLine = "第一行n" +
"第二行n" +
"第三行";
// 原始多行字符串(更简洁)
string newMultiLine = """
第一行
第二行
第三行
""";
// JSON 示例
string json = """
{
"name": "John Doe",
"age": 30,
"email": "john@example.com"
}
""";
从可读性看,这种差异非常明显。特别是当文本内部已经包含大量双引号时,原始字符串的优势会进一步放大。
真正容易踩坑的两条规则:缩进与引号数量
原始字符串看上去简单,但在实际使用中,最需要理解的不是“能不能写”,而是“编译器如何判断边界”。这里主要有两件事:缩进处理,以及分隔符双引号数量规则。
缩进是如何处理的
多行原始字符串会根据结束分隔符的位置,自动决定要去掉多少公共缩进。这样既能让代码在方法体内保持整齐,又不会把无意义的左侧空白一并带进结果。
string GetFormattedMessage()
{
return """
Hello,
This is an indented message.
It preserves the formatting exactly as written.
""";
}
// 输出结果:
// Hello,
// This is an indented message.
// It preserves the formatting exactly as written.
这意味着你可以在代码里保持缩进层次,同时让真正的文本格式仍然可控。对于消息模板、提示词、Markdown 片段等内容,这个行为很实用。
最小缩进的判断方式
如果把结束引号放在某个缩进层级,编译器会按这个层级去掉每一行对应的公共前导空白:
string minimalIndent = """
First line
Second line
Third line
"""; // 结束引号的缩进决定了最小缩进
// 等效于:
// "First linenSecond linenThird line"
因此,多行原始字符串是否“看起来对齐”,直接影响最终内容。团队里如果同时写模板和配置,最好统一缩进习惯。
内容里有很多引号时怎么办
如果字符串内容本身包含连续的双引号,那么分隔符就必须比内容中的最长连续双引号更多。原文里的原则可以直接记成一条公式:N > M。
分隔符的双引号数量
N必须大于内容中连续双引号的最大数量M最小分隔符数量为 3
判断规则就是
N > M
// 内容包含三个双引号 → 使用四个作为分隔符
string tripleQuotes = """" 这里可以包含 """ 三个引号 """";
// 内容包含四个双引号 → 使用五个作为分隔符
string fourQuotes = """"" 这里可以包含 """" 四个引号 """"";
这条规则看起来有点绕,但记忆成本其实不高:内容里连续引号越多,包裹它的分隔符就再多一个。
和字符串插值一起用时,重点看 $ 和大括号
原始字符串并不排斥插值,反而非常适合生成动态 JSON、代码模板和配置片段。这里的关键在于:$ 的数量决定了插值表达式的边界。

一个
$,表达式写成{ }两个
$$,表达式写成{{ }}继续增加时,规则以此类推
string name = "John";
int age = 30;
// 基本插值
string interpolated = $$"""
{
"name": "{{name}}",
"age": {{age}}
}
""";
// 复杂插值(使用多个$符号)
string complex = $$$"""
{
"name": "{{{name}}}", // 注意:这里需要三个大括号
"age": {{age}}
}
""";
这套设计的意义在于,字符串本体里如果原本就有很多大括号,例如 JSON、模板语法、代码生成脚本,编译器仍然能区分哪些是文本,哪些是要计算的表达式。
为什么它特别适合模板输出
传统插值字符串一旦叠加 JSON 或代码模板,往往很快会出现转义、大括号嵌套、换行混乱三件套。原始字符串把这些问题拆开了:
引号保留原样
换行保留原样
通过
$数量控制插值边界
这也是它在模板生成类代码中最有价值的地方。
哪些场景最适合用原始字符串
如果只是存一个很短的提示文本,原始字符串未必有必要。但只要文本带结构、带格式、带嵌套符号,它就会明显更顺手。下面几类是最典型的使用场景。
JSON 和 XML
// JSON 配置
string configJson = """
{
"appSettings": {
"timeout": 30,
"retryCount": 3,
"apiUrl": "https://api.example.com"
},
"logging": {
"level": "Information",
"filePath": "logs/app.log"
}
}
""";
// XML 文档
string xmlDocument = """
Gambardella, Matthew
XML Developer's Guide
Computer
44.95
2000-10-01
An in-depth look at creating applications with XML.
""";
这类内容本身就强调层级和格式。用原始字符串后,代码里的文本结构和实际输出高度一致,阅读成本会低很多。
正则表达式
// 复杂的正则表达式(无需转义反斜杠)
string emailPattern = """^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$""";
string phonePattern = """^(+d{1,3}s?)?((d{1,4})s?)?d{1,4}[s.-]?d{1,4}[s.-]?d{1,9}$""";
// 使用正则表达式
bool isValidEmail = Regex.IsMatch("test@example.com", emailPattern);
正则本来就充满反斜杠和边界符,一旦还要再为 C# 字符串做二次转义,可读性会迅速下降。原始字符串在这里的价值非常直接:让模式本身更接近你真正想表达的内容。
SQL 查询
string sqlQuery = """
SELECT
u.Id,
u.Name,
u.Email,
COUNT(o.Id) AS OrderCount
FROM Users u
LEFT JOIN Orders o ON u.Id = o.UserId
WHERE u.IsActive = 1
GROUP BY u.Id, u.Name, u.Email
HA VING COUNT(o.Id) > 0
ORDER BY OrderCount DESC
""";
多行 SQL 是另一个明显收益点。查询结构、字段列表、连接条件和排序语句都能按数据库常见格式排版,不必在字符串拼接中来回找错。
代码生成与模板拼装
string GenerateCSharpClass(string className, IEnumerable<string> properties)
{
return $$"""
public class {{className}}
{
{{string.Join("n ", properties.Select(p => $"public string {p} {{ get; set; }}"))}}
public {{className}}()
{
}
public override string ToString()
{
return $"{{
{{string.Join(", ", properties.Select(p => $"{p}={{{p}}}"))}}
}}";
}
}
""";
}
// 使用示例
string classCode = GenerateCSharpClass("Person", new[] { "FirstName", "LastName", "Age" });
这类场景最能体现“原始字符串 + 插值”的组合价值。模板结构保持完整,变量替换也足够直观,后续扩展字段、方法或注释时不容易把字符串本身改乱。
怎么判断项目里该不该用
原始字符串字面量不是要替代所有字符串写法,而是更适合那些“文本本身就值得被看清”的内容。它最突出的优势包括:
消除大量转义需求
原生支持多行内容
能配合缩进规则保持代码整洁
支持与插值结合生成动态文本
编译时处理,无额外运行时性能负担
优先考虑使用的情况
JSON/XML/HTML等结构化文本复杂正则表达式
多行
SQL查询代码生成和模板输出
需要保留格式的文档字符串
如果字符串很短、没有特殊字符,也不涉及多行或模板,普通字符串依然足够简单。但只要你开始频繁写 \、\"、+ 拼接,或者为了排版牺牲可读性,就可以优先考虑改用 C# 11 的原始字符串字面量。









