Lesson 13: C# 11 — UTF-8 字符串字面量 + 总结

编译期编码 UTF-8——消除热路径上的隐形 GC 压力

前置:已完成 Lesson 11(Raw String Literals)Lesson 12(List Patterns + Required Members)
本课:UTF-8 String Literals + C# 11 四特性组合拳 + 总结。

一、UTF-8 String Literals UTF-8 字符串字面量

1.1 痛点——隐形的编码税

😣 C# 10

// HTTP 头、JSON 序列化——每次都要 UTF-16→UTF-8
byte[] data = Encoding.UTF8.GetBytes("application/json");
var span = new ReadOnlySpan<byte>(data);
// 堆分配 + 编码开销——每秒 5 万次 = Gen 0 GC

😎 C# 11

// 编译期编码,零运行时开销
ReadOnlySpan<byte> contentType = "application/json"u8;
// 字节直接嵌入 PE 数据段——无堆分配、无 GC

1.2 为什么网络世界选 UTF-8 而不是 UTF-16?

中英文逐字符对比互有胜负——没有谁绝对"更小"。UTF-8 赢在网络,靠三个实用理由:

优势说明
ASCII 兼容HTTP 头、JSON key、HTML 标签全是 ASCII——UTF-8 对 ASCII 原样 1 字节。UTF-16 的 41 00 会被老工具当成 A + 空字符
无字节序问题UTF-16 有 Big/Little Endian 之争(需 BOM)。UTF-8 字节顺序固定
自同步UTF-8 每字节有明确"角色":首字节标识序列长度,续字节全是 10xxxxxx。从中间开始读也能快速定位字符边界

1.3 本质——不是 byte[],是 ReadOnlySpan<byte>

属性说明
类型ReadOnlySpan<byte>——零分配、栈上即可用
求值时机编译期——编译器直接编码为 UTF-8 字节序列
存储位置PE 文件元数据——和字符串常量一样
运行时开销——IL 为 ldsflda(加载静态字段地址)

1.4 性能——不是 12ns 的事,是 GC 的事

// 10000 RPS 服务,每请求 5 个 UTF-8 常量:
//   传统:每秒 50000 次 GetBytes → ~1 MB/s 临时分配 → Gen 0 GC 频繁 → P99 毛刺
//   "..."u8:0 分配 → GC 完全不知道

"hello"u8 消除的不是 12 纳秒编码时间,而是累积分配引发的 GC 暂停。System.Text.Json 内部全部用它——这就是比 Newtonsoft.Json 快的底层原因之一。

1.5 真实场景

// ✅ HTTP Headers / Content-Type
request.Content = new StringContent(body, Encoding.UTF8, "application/json"u8);

// ✅ 高性能序列化
var writer = new Utf8JsonWriter(stream);
writer.WriteString("name"u8, "Alice"u8);

// ✅ 常量 MIME type
ReadOnlySpan<byte> jsonType = "application/json"u8;
ReadOnlySpan<byte> authHeader = "Authorization"u8;

// ✅ 配合 Raw String Literal——多行 JSON 直接编译为 UTF-8 字节
ReadOnlySpan<byte> json = """
    {"name": "Alice", "age": 30}
    """u8;

1.6 限制

限制说明
只接受编译时常量不能 $"hello {name}"u8——内插后不是常量
是 ReadOnlySpan,不是 byte[].ToArray() 转 byte[]
不能是 constconst ReadOnlySpan x = "..."u8 不合法——ref struct 不能是常量

二、组合拳——四个特性一起用的样子

// 高性能 Minimal API——C# 11 四特性齐上

public class CreateUserRequest
{
    public required string Name { get; init; }    // Required Members
    public required string Email { get; init; }
    public string[] Tags { get; init; } = [];
}

app.MapPost("/api/users", (CreateUserRequest req) =>
{
    // List Patterns:标签判断
    var desc = req.Tags switch
    {
        [] => "无标签",
        [var single] => $"标签: {single}",
        ["vip", ..] => "VIP",
        _ => "多标签",
    };

    // Raw String Literal + UTF-8:零开销 JSON 响应
    var response = $$"""
        { "name": "{{req.Name}}", "desc": "{{desc}}" }
        """u8;
    return Results.Bytes(response, "application/json"u8);
});

三、C# 11 三课总结

课程特性解决的痛点
11. Raw String Literals"""...""" 原始字符串转义地狱、拼接地狱、多行字符串
12. List Patterns + Required集合模式匹配、编译期必填数组检查啰嗦、"必填属性"靠嘴说
13. UTF-8 + Combo(本课)"..."u8 编译期 UTF-8热路径 UTF-8 编码开销 → GC
C# 11 的定位:不做颠覆(那是 C# 8/9/12 的事),填补日常摩擦。Raw String Literals 让你写 JSON/SQL/XML 不用转义,List Patterns 让集合匹配可读,Required 把运行时报错提前到编译时,UTF-8 Literals 消除热路径编码开销——每一个都是你之前"没办法,忍了"的地方。

四、小测验

1/3 · "hello"u8 返回什么类型?
2/3 · UTF-8 在网络协议中胜出,主要因为什么?
3/3 · "hello"u8 的真正性能价值是什么?

五、下一步

💬 有问题?对 UTF-8 的使用场景不明确?C# 11 四个特性还有困惑?随时问。