尝试新的 System.Text.Json 源生成器
尝试新的 System.Text.Json 源生成器
在 .NET 6.0 的预览版中,我们使用 System.Text.Json 发布了一个新的 C# source generator 来帮助改进应用程序的性能。这里我们将回顾为什么我们要构建它,以及在你的应用程序中可以获得何种优势。
对于 System.Text.Json 源生成器,在 .NET 中,我们现在有一些用于 JSON 序列化的模型可供选择, JsonSerializer 是现有的模型,提供运行时反射支持,另外有两个编译时的源生成模型;这种方式的生成器生成优化后的序列化模型。或者两者都有。在所有的源生成场景中,生成的组件被直接传送给 JsonSerializer 做性能优化。下面是每种序列化模型提供的功能概览。
JsonSerializer | JsonSerializer + pre-generatiing optimized serialization login | JsonSerializer + pre-generating data access model | |
---|---|---|---|
Increases serialization throughput | No | Yes | No |
Reduces start-up time | No | Yes | Yes |
Reduces private memory usage | No | Yes | Yes |
Eliminates runtime reflection | No | Yes | Yes |
Facilitates trim-safe app size reduction | No | Yes | Yes |
Supports all serialization features | Yes | No | Yes |
源生成器入门
源生成器可以用于任何 .NET 的 C# 项目,包括控制台应用,类库,Web 应用,以及 Blazor 应用程序。可以通过 System.TExt.Json 的 NuGet 包。在 .NET 6.0 即将到来的预览版 7 中将不再需要。
除了 .NET 6.0,源生成器与其它的 TFM 兼容,即 .NET 5.0 和更低的版本,.NET Framework 和 .NET Standard。API 集在各种 TFM 之间是一致的,但是不同 TFM 的可用的框架 API 不同,内部的实现是不同的。
对 .NET SDK 来说,最低的支持版本是 .NET 5.0
什么是 System.Txt.Json 源生成器
几乎所有的 .NET 序列化器的核心都是反射。反射对于特定场景提供了优异的能力,但是不包括作为高性能云原生应用程序 (特别) 基础的序列化/反序列,以及处理大量的 JSON 文档的场景。对于启动、内存使用和程序集修剪,反射都是问题所在。
一种方案是将运行时反射替换为编译时源生成。源生成器生成 C# 源代码文件,它可以被编译为库或者应用程序的一部分。在编译时生成代码对于 .NET 应用程序可以带来许多优势,包括增强性能。在 .NET 6 中,作为 System.Text.Json 的一部分,我们包括了新的源生成器。JSON 源生成器与 JsonSerializer 协同工作,可以通过多种方式配置。现有的 JsonSerializer 功能不变,所以是否使用新的源生成器是你的选择。JSON 源生成器的工作方式是将 JSON 可序列化类型的运行时检查转换到编译时,它生成一个静态模型来访问这些类型中的数据,直接使用 Utf8JsonWriter 优化序列化逻辑,或者同时使用两者。
为了使用源生成器,你可以定义一个派生于被称为 JsonSerializerContext 的新类型的分部类,使用新的 JsonSerializableAttribute 类来标注可序列化类型。
例如,对于一个简单的 Person 类型进行序列化。
namespace Test
{
internal class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
}
需要为源生成器如下指定类型。
using System.Text.Json.Serialization;
namespace Test
{
[JsonSerializable(typeof(Person))]
internal partial class MyJsonContext : JsonSerializerContext
{
}
}
作为构建过程的一部分,源生成器会将 MyJsonContext 分部类填充为如下类型。
internal partial class MyJsonContext : JsonSerializerContext
{
public static MyJsonContext Default { get; }
public JsonTypeInfo<Person> Person { get; }
public MyJsonContext(JsonSerializerOptions options) { }
public override JsonTypeInfo GetTypeInfo(Type type) => ...;
}
生成的源代码可以通过将它直接传递给 JsonSerializer 的新的重载方法中,以集成到编译的应用程序中。
// 序列化
Person person = new() { FirstName = "Jane", LastName = "Doe" };
byte[] utf8Json = JsonSerializer.SerializeToUtf8Bytes(
person, MyJsonContext.Default.Person);
// 反序列化
person = JsonSerializer.Deserialize(utf8Json, MyJsonContext.Default.Person):
为什么使用源生成器?
JsonSerializer 是将 .NET 对象序列化到 JSON 串,以及从 JSON 串反序列回到 .NET 对象的工具。为了处理某种类型,序列化器需要得到如何访问对象成员的信息。在序列化的时候,序列化器需要访问对象的属性和字段读取器。简单来说,在反序列化的时候,序列化器需要访问构造函数来实例化对象,以及对象的字段与属性的设置器。
System.Text.Json 暴露在使用 JsonSerializer 进行序列化和反序列化时影响的机制,通过 JsonSerializerOptions (它支持运行时配置),还可以通过像 [JsonPropertyName(string)] 与 [JsonIgnore] 这样的特性 ( 这支持设计时配置 )。在序列化和反序列化类型的实例的时候,序列化器需要这些配置的信息,以便其使用。
当处理 JSON 可序列化类型的时候,序列化器需要在一个结构中访问关于对象成员和特性的配置信息,优化的格式。我们可以引用此结构化的信息作为必要的序列化元数据来序列化类型。
在以前版本的 System.Text.Json 中,序列化元数据只能在运行时得出,在对每种类型进行第一次序列化和反序列化的时候得出传递给序列化器。在这些元数据生成之后,序列化器执行实际的序列化和反序列化。这里得到的元数据将被缓存并在后继的 JSON 处理中被重用。生成元数据的环节基于反射实现,计算的复杂性在事件和分配上都是昂贵的。我们可以将该阶段称为序列化器的 “热身” 阶段。
System.Text.Json 源生成器帮助我们移除了该热身阶段,通过将运行时中的使用反射的可序列化类型检查转移到编译时进行。检查的结果可以作为源代码,用来初始化结构化的序列化元数据实例。生成器还可以生成高度优化的序列化逻辑,得益于一套 序列特性,指定的预先处理,默认情况下,生成器生成两种类型的源,但是可以配置为仅仅生成一套类型,或者每种可序列化类型一种。
生成的元数据包含在编译生成的程序集中,可以被初始化并直接传递给 JsonSerializer 以便序列化器不需要在运行时生成。这有助于缩减每种类型第一次序列化或者反序列化的时间。使用这些特性,使用该源生成器可以提供如下优势:
- 增强序列化的吞吐量
- 缩减启动时间
- 缩减私有内存的使用
- 移除对 System.Reflection 和 System.Reflection.Emit 的使用
- 剪裁兼容的序列化,可以缩减应用程序的尺寸
JsonTypeInfo<T>
, JsonTypeInfo
, 与JsonSerializerContext 简介
Try the new System.Text.Json source generator | .NET Blog (microsoft.com)