Lesson 13: C# 11 — UTF-8 字符串字面量 + 总结
编译期编码 UTF-8——消除热路径上的隐形 GC 压力
一、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[] |
| 不能是 const | const 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 三课总结
C# 11 的定位:不做颠覆(那是 C# 8/9/12 的事),填补日常摩擦。Raw String Literals 让你写 JSON/SQL/XML 不用转义,List Patterns 让集合匹配可读,Required 把运行时报错提前到编译时,UTF-8 Literals 消除热路径编码开销——每一个都是你之前"没办法,忍了"的地方。
四、小测验
五、下一步
💬 有问题?对 UTF-8 的使用场景不明确?C# 11 四个特性还有困惑?随时问。