前言
System.Text.Json 从 .NET 6 开始提供源生成器(source generation)模式:编译时生成序列化代码,运行时不再做反射。对启动时间敏感的 CLI 工具、AOT 编译应用和高吞吐 API 服务都有明显收益。
本文讲源生成器的两种模式、与反射模式的差异和迁移要点,示例基于 .NET 8。
一、反射模式的隐性成本
默认的 JsonSerializer 反射模式在第一次序列化某个类型时构建元数据:扫描属性、生成委托、缓存起来。这带来几个问题:
- 首次请求延迟尖峰,冷启动的服务尤其明显。
- 反射元数据占用内存,类型多时可观。
- Native AOT / trimming 场景反射不可用或易被裁剪。
源生成器把「构建元数据」这一步提前到编译期,运行时只剩直接调用。
二、两种模式
元数据提取模式:生成序列化元数据,替换运行时反射,普通 JIT 应用首选。序列化优化模式:进一步生成强类型序列化代码,路径更快但会增大二进制体积。
[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(Order))]
[JsonSerializable(typeof(List<Order>))]
internal partial class OrderJsonContext : JsonSerializerContext {{ }}
// 使用:传入 Context 类型
string json = JsonSerializer.Serialize(order, OrderJsonContext.Default.Order);
var parsed = JsonSerializer.Deserialize(json, OrderJsonContext.Default.Order);
关键点:所有要序列化的类型(包括集合元素类型、嵌套类型)都要用 JsonSerializable 声明,漏声明的类型在运行时抛 NotSupportedException——这个报错编译期就应发现,所以类型清单要随模型一起维护。
三、与 ASP.NET Core 集成
builder.Services.AddControllers().AddJsonOptions(options =>
{{
// MVC 内部仍用反射模式;要全局启用源生成需要在最小 API 场景配合 TypedResults
}});
如实说明:MVC 控制器管线目前对源生成器的支持有限,收益最明显的场景是最小 API + TypedResults、手动序列化、信号协议。Web 项目里最实际的做法是:把频繁序列化的热点 DTO 单独走 JsonContext,其余保持默认,渐进迁移。
四、配置与反射模式的差异
源生成模式下的配置集中在 JsonSourceGenerationOptions 特性里:命名策略(camelCase)、缩进、默认值忽略、枚举转字符串等。部分运行时选项(如自定义 Converter 实例)不能直接用,要用 JsonConverter 特性挂在类型上。
| 能力 | 反射模式 | 源生成 |
|---|---|---|
| 任意类型直接序列化 | 支持 | 需要显式声明 |
| 运行时切换命名策略 | JsonSerializerOptions 即时生效 | 编译期固定(可生成多个 Context) |
| 启动延迟 | 首次序列化有尖峰 | 无 |
| Native AOT | 不支持 | 支持 |
| 二进制体积 | 最小 | 随类型数量增长 |
五、迁移要点
- 先给热点 DTO 建 Context,跑全量序列化回归测试,确认输出和反射模式一致(默认设置下应当一致)。
- 异步序列化(SerializeAsync)配合流式场景在源生成下同样可用。
- 动态类型、object 属性、多态序列化在源生成下的限制更多,遇到时评估是否留在反射模式。
- 版本升级 .NET 9/10 后留意生成器的行为变化,Context 类要重新编译而非沿用旧 DLL。
六、生产使用注意事项
- 类型清单加进 CI 检查:新增 DTO 忘记声明时,单测应当在开发期就报错而不是线上。
- 对比收益时分别测「冷启动首请求」和「稳态吞吐」,两个指标受益幅度不同。
- 注意 Context 生成的代码对程序集内 partial class 的要求,跨程序集复用要把 Context 放在被序列化类型可达的位置。
- 序列化选项变更(如加字段)后确认契约测试,特别是对外 API。
总结
System.Text.Json 源生成器把序列化的运行时反射成本换成编译期生成:启动更快、内存更省、AOT 可用,代价是类型要显式声明、运行时配置灵活性下降。
推荐的落地顺序是:新项目从第一个 DTO 就建 Context 养成习惯;存量项目先给启动敏感或高吞吐的热点类型接入,回归测试确认输出一致后逐步铺开。序列化是性能优化的第二站,先测再改。










暂无评论内容