如何在 System.Text.Json 中选择反射或源生成
默认情况下,System.Text.Json
使用运行时反射来收集访问对象属性以进行序列化和反序列化时所需的元数据。 作为替代方法,System.Text.Json
6.0 及更高版本可以使用 C# 源生成功能来提高性能、降低专用内存使用量以及推动程序集修整,从而缩小应用大小。
可以在面向早期框架的项目中使用版本 6.0+ 的 System.Text.Json
。 有关详细信息,请参阅如何获取库。
本文介绍了这些选项,并提供了有关如何针对方案选择最佳方法的指导。
概述
基于每个模式的如下优点来选择反射或源生成模式:
好处 | 反射 | 源生成: 元数据集合 |
源生成: 序列化优化 |
---|---|---|---|
简化的编码和调试。 | ✔️ | ❌ | ❌ |
支持非公共访问器。 | ✔️ | ❌ | ❌ |
支持所需的属性。 | ✔️ | ✔️ | ✔️ |
支持 init-only 属性。 | ✔️ | ✔️ | ✔️ |
支持所有可用的序列化自定义。 | ✔️ | ❌ | ❌ |
缩短启动时间。 | ❌ | ✔️ | ❌ |
降低专用内存使用量。 | ❌ | ✔️ | ✔️ |
消除运行时反射。 | ❌ | ✔️ | ✔️ |
有助于缩小可修整的应用的大小。 | ❌ | ✔️ | ✔️ |
增加序列化吞吐量。 | ❌ | ❌ | ✔️ |
概述
基于每个模式的如下优点来选择反射或源生成模式:
好处 | 反射 | 源生成: 元数据集合 |
源生成: 序列化优化 |
---|---|---|---|
简化的编码和调试。 | ✔️ | ❌ | ❌ |
支持非公共访问器。 | ✔️ | ❌ | ❌ |
支持所需的属性。 | ✔️ | ❌ | ❌ |
支持 init-only 属性。 | ✔️ | ❌ | ❌ |
支持所有可用的序列化自定义。 | ✔️ | ❌ | ❌ |
缩短启动时间。 | ❌ | ✔️ | ❌ |
降低专用内存使用量。 | ❌ | ✔️ | ✔️ |
消除运行时反射。 | ❌ | ✔️ | ✔️ |
有助于缩小可修整的应用的大小。 | ❌ | ✔️ | ✔️ |
增加序列化吞吐量。 | ❌ | ❌ | ✔️ |
以下部分介绍了这些选项及其相对优点。
System.Text.Json 元数据
若要对类型进行序列化或反序列化,JsonSerializer 需要关于如何访问该类型的成员的信息。 JsonSerializer
需要以下信息:
- 如何访问属性 getter 和字段以进行序列化。
- 如何访问构造函数、属性资源库和字段以进行序列化。
- 有关用于自定义序列化或反序列化的特性的信息。
- JsonSerializerOptions 中的运行时配置。
此信息被称为“元数据”。
默认情况下,JsonSerializer
使用反射在运行时收集元数据。 每当 JsonSerializer
需要首次对类型进行序列化或反序列化时,它将收集并缓存此元数据。 元数据收集进程将耗费时间并占用内存。
源生成 - 元数据收集模式
可以使用源生成将元数据收集进程从运行时移到编译时。 在编译期间,系统将收集元数据并生成源代码文件。 生成的源代码文件会自动编译为应用程序的一个整型部分。 使用此方法便无需进行运行时元数据集合,这可提高序列化和反序列化的性能。
源生成提供的性能改进可能很大。 例如,测试结果显示出可获得高达 40% 或更高的启动时间缩减、专用内存缩减、吞吐速度增加(在序列化优化模式下)和应用大小减小。
已知问题
在任一序列化模式下,默认只支持 public
属性和字段。* 但是,反射模式支持使用private
访问器,而源生成模式不支持。 例如,可将 JsonInclude 特性应用于具有 private
setter 或 getter 的属性,并且该特性将在反射模式下序列化。 源生成模式只支持 public
属性的 public
或 internal
访问器。 如果在非公共访问器上设置 [JsonInclude]
并选择源生成模式,则会在运行时引发 NotSupportedException
。
在反射和源生成模式下:
- 仅支持
public
属性和public
字段*。 - 只有
public
构造函数可用于反序列化。
*从 .NET 7 开始,可使用自定义 JSON 协定在序列化中包含 private
属性和字段。
有关源生成的其他已知问题的信息,请参阅 dotnet/runtime 存储库中标记为“source-generator”的 GitHub 问题。
序列化优化模式
JsonSerializer
具有很多自定义序列化输出的功能,如 camel 大小写属性名称和保留引用。 对所有这些功能的支持会导致一些性能开销。 源生成可以通过生成直接使用 Utf8JsonWriter
的优化代码来提高序列化性能。
该优化代码不支持 JsonSerializer
所有序列化功能。 如果指定了不受支持的选项,则序列化程序将检测是否可以使用该优化代码并回退到默认序列化代码。 例如,JsonNumberHandling.AllowReadingFromString 不适用于写入,因此,指定此选项不会导致回退到默认代码。
下表显示优化的序列化代码所支持的 JsonSerializerOptions
中的选项:
序列化选项 | 受优化代码支持 |
---|---|
Converters | ❌ |
DefaultIgnoreCondition | ✔️ |
DictionaryKeyPolicy | ❌ |
Encoder | ❌ |
IgnoreNullValues | ❌ |
IgnoreReadOnlyFields | ✔️ |
IgnoreReadOnlyProperties | ✔️ |
IncludeFields | ✔️ |
NumberHandling | ❌ |
PropertyNamingPolicy | ✔️ |
ReferenceHandler | ❌ |
TypeInfoResolver | ✔️ |
WriteIndented | ✔️ |
下表显示优化的序列化代码所支持的特性:
如果为某一类型指定了不受支持的选项或特性,则序列化程序将回退到默认的 JsonSerializer
代码。 在这种情况下,不会在序列化该类型时使用优化代码,但可以将优化代码用于其他类型。 因此,请务必使用选项和工作负载进行性能测试,以确定序列化优化模式实际可带来多少益处。 此外,回退到 JsonSerializer
代码的功能需要使用元数据收集模式。 如果只选择了序列化优化模式,则对于需要回退到 JsonSerializer
代码的类型或选项,序列化可能会失败。
如何使用源生成模式
大多数 System.Text.Json 文档均会演示如何编写使用反射模式的代码。 有关如何使用源生成模式的信息,请参阅如何在 System.Text.Json 中使用源生成。