Lesson 11: C# 11 — 原始字符串字面量 Raw String Literals

不再转义、不再拼接——所见即所得的多行字符串

前置:已完成 Lesson 10(C# 10 小特性集)
C# 11 ↔ .NET 7:2022.11 发布。.NET 7 不是 LTS,但 C# 11 特性是纯语言级别——在 .NET 6/8 上设 LangVersion=11 即可使用。
本课只讲 Raw String Literals——List Patterns、Required Members、UTF-8 String Literals 在 Lesson 1213

一、字符串地狱三兄弟

😣 C# 10 及以前

// ① 转义地狱:每个引号加反斜杠
var json = "{\"name\":\"Alice\"}";

// ② 拼接地狱:多行字符串用 +
var sql = "SELECT Id " +
         "FROM Users " +
         "WHERE Status = 1";

// ③ Verbatim(@)只解决一半
var xml = @"<item name=""book""/>";
// 引号还是要双双写

😎 C# 11

// 所见即所得——至少三个双引号
var json = """
    {"name": "Alice"}
    """;

var sql = """
    SELECT Id
    FROM Users
    WHERE Status = 1
    """;

var xml = """
    <item name="book"/>
    """;

二、核心规则

规则说明
至少三个 "开始/结束各至少 3 个双引号——"""..."""""""...""""
内容中 " 少于包裹数就安全内容含 3 个连续引号 → 用 4 个 """" 包裹;内容含 4 个 → 用 5 个……以此类推
开始 """ 后必须换行内容从下一行开始——"""Hello""" 不合法(开始引号后无换行)
自动缩进修剪编译器以结束标记的缩进为基准,去掉每行前面共同的空白
支持内插$"""...{x}..."""——内插正常使用;$$"""...{{x}}..."""——减少花括号冲突

缩进修剪——编译器怎么决定去留?

// 你写(结束 """ 在第 4 列缩进):
var text = """
        Line 1
            Line 2
        Line 3
    """;

// 编译器:结束 """ 从第 4 列开始 → 基准缩进 = 4 格 → 每行去掉 4 格
// Line 1
//     Line 2    ← 这行额外 4 格保留(相对于基准缩进)
// Line 3
基准只看结束 """ 的位置。如果结束标记在深层缩进里(如 8 格),每行都去掉 8 格。如果某行实际少于 8 格空白——编译器报错。

末尾换行——不可能三角

// 正常写法:结束 """ 独占一行 → 末尾自动包含 \r\n
var s = """
    hello
    """;  // → "hello\r\n"  ← 末尾有换行

// 无末尾换行:结束 """ 紧跟内容 → 缩进修剪失效
var s = """
    hello""";  // → "    hello"  ← 缩进保留了!基准=0

// 折衷:正常写,末尾 Trim
var s = """
    hello
    """.TrimEnd("\r\n");  // → "hello"  ← TrimEnd 对 ReadOnlySpan 零分配
不可能三角:缩进修剪好用 / 无末尾换行 / 结束 """ 独占一行——三者只能取其二。大多数场景(JSON、SQL、HTML)末尾多个换行不碍事。

三、编译器生成——零运行时开销

// 你写:
var json = """
    {"name": "Alice"}
    """;

// 编译器生成(简化):
//   - 编译时完成缩进去除(纯编译器预处理)
//   - IL:ldstr "{\"name\": \"Alice\"}\r\n"
//   - 和手写转义字符串完全一样的 IL
//   - 内插版本 → DefaultInterpolatedStringHandler(和普通 $"..." 一样)

Raw String Literal 是纯粹的编译时预处理。IL 和手写转义字符串一模一样——它只是让你写的时候不用转义。

IL 中的 \r\n源文件 Windows 换行(CRLF)就是内容的一部分。编译器只修剪缩进空格/制表符,不修剪换行符——因为换行符是你敲的 Enter,是内容的分隔符。

四、真实场景

// ✅ 单元测试中的期望 JSON
var expected = """
    {
        "id": 1,
        "name": "Alice"
    }
    """;

// ✅ SQL 查询模板
var sql = """
    SELECT u.Id, u.Name, o.Total
    FROM Users u
    JOIN Orders o ON u.Id = o.UserId
    WHERE u.Status = @status
    """;

// ✅ $ 内插——配合 JSON/XML 使用
var name = "Alice";
var json = $"""
    {{ "name": "{name}", "ts": "{DateTime.Now:O}" }}
    """;  // $ + """ → {name} 是内插,字面 { 需 {{ 转义

// ✅ $$ 内插——避免和 JSON 花括号冲突
var json2 = $$"""
    { "name": "{{name}}", "count": {{count}} }
    """;  // $$ → {{name}} 是内插,{ 是字面

五、小测验

1/3 · var s = """Hello"""; 能编译吗?
2/3 · 缩进修剪的基准是什么?
3/3 · $"""..."""$$"""...""" 的区别是?

六、下一步

💬 有问题?对缩进修剪规则不清楚?不确定什么时候用 4 个引号而不是 3 个?随时问。