.NET序列化技能 dotnet-serialization

这个技能覆盖 .NET 应用程序中的序列化技术,包括 System.Text.Json 源生成器、Protocol Buffers (Protobuf) 和 MessagePack,提供 AOT 安全的序列化模式、性能优化指导,适用于后端开发、API 构建、高性能数据交换和云原生场景。关键词:.NET, 序列化, AOT, System.Text.Json, Protobuf, MessagePack, 性能优化, 后端开发, 云计算, DevOps

后端开发 0 次安装 0 次浏览 更新于 3/6/2026

name: dotnet-serialization description: “序列化数据。System.Text.Json 源生成器、Protobuf、MessagePack、AOT 安全模式。” user-invocable: false

dotnet-serialization

为 .NET 应用程序提供的 AOT 友好序列化模式。覆盖 System.Text.Json 源生成器用于编译时序列化、Protocol Buffers (Protobuf) 用于高效二进制序列化,以及 MessagePack 用于高性能紧凑二进制格式。包括性能权衡指导以选择合适的序列化器,以及关于在 AOT 场景中基于反射的序列化的警告。

范围

  • System.Text.Json 源生成器用于编译时序列化
  • Protocol Buffers (Protobuf) 用于二进制序列化
  • MessagePack 用于高性能紧凑格式
  • 序列化器选择的性能权衡指导
  • AOT 安全序列化模式和反模式

超出范围

  • 源生成器编写模式 – 见 [技能:dotnet-csharp-source-generators]
  • HTTP 客户端工厂和弹性管道 – 见 [技能:dotnet-http-client] 和 [技能:dotnet-resilience]
  • Native AOT 架构和裁剪 – 见 [技能:dotnet-native-aot] 和 [技能:dotnet-trimming]

交叉引用: [技能:dotnet-csharp-source-generators] 用于理解 STJ 源生成器的工作原理。见 [技能:dotnet-integration-testing] 用于测试序列化往返正确性。


序列化格式比较

格式 AOT 安全 人类可读 相对大小 相对速度 最适合
JSON System.Text.Json (源生成器) 最大 良好 API、配置、Web 客户端
Protobuf Google.Protobuf 最小 最快 服务间通信、gRPC 线格式
MessagePack MessagePack-CSharp 是 (带 AOT 解析器) 高吞吐量缓存、实时
JSON Newtonsoft.Json (反射) 最大 较慢 仅遗留 – 不用于 AOT

何时选择什么

  • System.Text.Json 带源生成器: 默认选择用于 API、配置和任何需要人类可读输出或 Web 客户端消费的场景。使用源生成器时 AOT 安全。
  • Protobuf: gRPC 的默认线格式。服务间通信的最佳吞吐量和最小负载大小。带有 .proto 文件的模式优先开发。
  • MessagePack: 当需要二进制紧凑性而不想管理 .proto 模式时。适用于缓存层、实时消息传递和高吞吐量场景,其中模式演进通过属性管理。

System.Text.Json 源生成器

System.Text.Json 源生成器生成编译时序列化代码,消除运行时反射。这是 Native AOT 必需 并强烈推荐用于所有新项目。见 [技能:dotnet-csharp-source-generators] 了解底层增量生成器机制。

基本设置

使用 [JsonSerializable] 属性定义 JsonSerializerContext,用于要序列化的每个类型:

using System.Text.Json.Serialization;

[JsonSerializable(typeof(Order))]
[JsonSerializable(typeof(List<Order>))]
[JsonSerializable(typeof(OrderStatus))]
public partial class AppJsonContext : JsonSerializerContext
{
}

使用生成上下文

// 序列化
string json = JsonSerializer.Serialize(order, AppJsonContext.Default.Order);

// 反序列化
Order? result = JsonSerializer.Deserialize(json, AppJsonContext.Default.Order);

// 带选项 (创建一次,重用)
var options = new JsonSerializerOptions
{
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    TypeInfoResolver = AppJsonContext.Default
};

string json = JsonSerializer.Serialize(order, options);

ASP.NET Core 集成

注册源生成上下文,使 Minimal API 自动使用。注意 ConfigureHttpJsonOptions 仅适用于 Minimal API – MVC 控制器需要通过 AddJsonOptions 单独配置:

var builder = WebApplication.CreateBuilder(args);

// Minimal APIs: ConfigureHttpJsonOptions
builder.Services.ConfigureHttpJsonOptions(options =>
{
    options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonContext.Default);
});

// MVC Controllers: AddJsonOptions (如果使用控制器)
builder.Services.AddControllers()
    .AddJsonOptions(options =>
    {
        options.JsonSerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonContext.Default);
    });

var app = builder.Build();

// Minimal API 端点自动使用注册上下文
app.MapGet("/orders/{id}", async (int id, OrderService service) =>
{
    var order = await service.GetAsync(id);
    return order is not null ? Results.Ok(order) : Results.NotFound();
});

app.MapPost("/orders", async (Order order, OrderService service) =>
{
    await service.CreateAsync(order);
    return Results.Created($"/orders/{order.Id}", order);
});

组合多个上下文

当应用程序有多个序列化上下文时 (例如,不同的有界上下文或库):

builder.Services.ConfigureHttpJsonOptions(options =>
{
    options.SerializerOptions.TypeInfoResolver = JsonTypeInfoResolver.Combine(
        AppJsonContext.Default,
        CatalogJsonContext.Default,
        InventoryJsonContext.Default
    );
});

常见配置

[JsonSourceGenerationOptions(
    PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
    WriteIndented = false)]
[JsonSerializable(typeof(Order))]
[JsonSerializable(typeof(List<Order>))]
public partial class AppJsonContext : JsonSerializerContext
{
}

处理多态性

[JsonDerivedType(typeof(CreditCardPayment), "credit_card")]
[JsonDerivedType(typeof(BankTransferPayment), "bank_transfer")]
[JsonDerivedType(typeof(WalletPayment), "wallet")]
public abstract class Payment
{
    public decimal Amount { get; init; }
    public string Currency { get; init; } = "USD";
}

public class CreditCardPayment : Payment
{
    public string Last4Digits { get; init; } = "";
}

// 注册基类型 -- 派生类型通过属性发现
[JsonSerializable(typeof(Payment))]
public partial class AppJsonContext : JsonSerializerContext
{
}

Protobuf 序列化

Protocol Buffers 提供模式优先二进制序列化。Protobuf 是 gRPC 的默认线格式,且 AOT 安全。

<PackageReference Include="Google.Protobuf" Version="3.*" />
<PackageReference Include="Grpc.Tools" Version="2.*" PrivateAssets="All" />

Proto 文件

syntax = "proto3";

import "google/protobuf/timestamp.proto";

option csharp_namespace = "MyApp.Contracts";

message OrderMessage {
  int32 id = 1;
  string customer_id = 2;
  repeated OrderItemMessage items = 3;
  google.protobuf.Timestamp created_at = 4;
}

message OrderItemMessage {
  string product_id = 1;
  int32 quantity = 2;
  double unit_price = 3;
}

独立 Protobuf (无 gRPC)

当需要缓存、消息传递或文件存储的紧凑负载时,使用 Protobuf 进行二进制序列化而无需 gRPC:

using Google.Protobuf;

// 序列化到字节
byte[] bytes = order.ToByteArray();

// 反序列化从字节
var restored = OrderMessage.Parser.ParseFrom(bytes);

// 序列化到流
using var stream = File.OpenWrite("order.bin");
order.WriteTo(stream);

Proto 文件注册在 .csproj 中

<ItemGroup>
  <Protobuf Include="Protos\*.proto" GrpcServices="Both" />
</ItemGroup>

MessagePack 序列化

MessagePack-CSharp 提供高性能二进制序列化,负载比 JSON 小,并与 .NET 良好集成。

<PackageReference Include="MessagePack" Version="3.*" />
<!-- 用于 AOT 支持 -->
<PackageReference Include="MessagePack.SourceGenerator" Version="3.*" />

基本用法带源生成器 (AOT 安全)

using MessagePack;

[MessagePackObject]
public partial class Order
{
    [Key(0)]
    public int Id { get; init; }

    [Key(1)]
    public string CustomerId { get; init; } = "";

    [Key(2)]
    public List<OrderItem> Items { get; init; } = [];

    [Key(3)]
    public DateTimeOffset CreatedAt { get; init; }
}

序列化

// 序列化
byte[] bytes = MessagePackSerializer.Serialize(order);

// 反序列化
var restored = MessagePackSerializer.Deserialize<Order>(bytes);

// 带压缩 (LZ4)
var lz4Options = MessagePackSerializerOptions.Standard.WithCompression(
    MessagePackCompression.Lz4BlockArray);
byte[] compressed = MessagePackSerializer.Serialize(order, lz4Options);

AOT 解析器设置

对于 Native AOT 兼容性,使用 MessagePack 源生成器生成解析器:

// 在项目中,源生成器自动从标注 [MessagePackObject] 的类型生成解析器。
// 在启动时注册生成解析器:
MessagePackSerializer.DefaultOptions = MessagePackSerializerOptions.Standard
    .WithResolver(GeneratedResolver.Instance);

反模式: 基于反射的序列化

在 Native AOT 或裁剪场景中,请勿使用基于反射的序列化器。 当链接器移除类型元数据时,基于反射的序列化会在运行时失败。

Newtonsoft.Json (JsonConvert)

Newtonsoft.Json (JsonConvert.SerializeObject / JsonConvert.DeserializeObject) 严重依赖运行时反射。它 不兼容 Native AOT 和裁剪:

// 错误: 基于反射 -- 在 AOT/裁剪下失败
var json = JsonConvert.SerializeObject(order);
var order = JsonConvert.DeserializeObject<Order>(json);

// 正确: 源生成 -- AOT 安全
var json = JsonSerializer.Serialize(order, AppJsonContext.Default.Order);
var order = JsonSerializer.Deserialize(json, AppJsonContext.Default.Order);

System.Text.Json 无源生成器

即使 System.Text.Json 在没有源生成上下文时也会回退到反射:

// 错误: 无上下文 -- 使用运行时反射
var json = JsonSerializer.Serialize(order);

// 正确: 显式上下文 -- 使用源生成代码
var json = JsonSerializer.Serialize(order, AppJsonContext.Default.Order);

从 Newtonsoft.Json 迁移路径

  1. JsonSerializer.Serialize / Deserialize 替换 JsonConvert.SerializeObject / DeserializeObject
  2. [JsonPropertyName] 替换 [JsonProperty]
  3. 用 System.Text.Json 的 JsonConverter<T> 替换 JsonConverter 基类
  4. 为所有序列化类型创建带有 [JsonSerializable]JsonSerializerContext
  5. JsonDocument / JsonElement 或强类型模型替换 JObject / JToken 动态访问
  6. 测试序列化往返 – 属性语义在库间不同

性能指导

吞吐量基准 (近似)

格式 序列化 (操作/秒) 反序列化 (操作/秒) 负载大小
Protobuf 最高 最高 最小
MessagePack
STJ 源生成器 良好 良好 较大 (文本)
STJ 反射 中等 中等 较大 (文本)
Newtonsoft.Json 较低 较低 较大 (文本)

优化技巧

  • 重用 JsonSerializerOptions – 创建选项成本高;创建一次并重用
  • 使用 JsonSerializerContext – 消除预热成本并减少分配
  • 使用 Utf8JsonWriter / Utf8JsonReader 用于流式场景,其中无需完全物化处理 JSON
  • **在 JSON 中使用 Protobuf ByteString 处理二进制数据,而不是 base64 编码字符串
  • 启用 MessagePack LZ4 压缩 用于大负载传输

关键原则

  • 默认为 System.Text.Json 带源生成器 用于所有 JSON 序列化 – 它是 AOT 安全、快速且内置框架的
  • 使用 Protobuf 用于服务间二进制序列化 – 尤其是作为 gRPC 的线格式
  • 使用 MessagePack 用于高吞吐量缓存和实时 – 当二进制紧凑性重要但不想管理 .proto 模式时
  • 在新 AOT 目标项目中切勿使用 Newtonsoft.Json – 它是基于反射的,不兼容裁剪
  • ASP.NET Core 中始终注册 JsonSerializerContext – 使用 ConfigureHttpJsonOptions 用于 Minimal API 和 AddJsonOptions 用于 MVC 控制器 (它们是分开的注册)
  • 标注所有序列化类型 – STJ 源生成器仅生成在 [JsonSerializable] 中列出的类型的代码;MessagePack 需要 [MessagePackObject]

见 [技能:dotnet-native-aot] 了解综合 AOT 编译管道,[技能:dotnet-aot-architecture] 了解 AOT 优先设计模式,以及 [技能:dotnet-trimming] 了解裁剪策略和 ILLink 描述符配置。


代理注意事项

  1. 在 AOT 项目中不要使用 JsonSerializer.Serialize(obj) 而无上下文 – 它会回退到反射并在运行时失败。始终传递源生成的 TypeInfo
  2. 不要忘记在 [JsonSerializable] 中列出集合类型[JsonSerializable(typeof(Order))] 不覆盖 List<Order>。单独添加 [JsonSerializable(typeof(List<Order>))]
  3. 不要将 Newtonsoft.Json [JsonProperty] 属性与 System.Text.Json 一起使用 – 它们会被静默忽略。改用 [JsonPropertyName]
  4. 不要在同一类型层次结构中混合 MessagePack [Key] 整数键和字符串键 – 选择一种策略并保持一致。
  5. 不要省略 <Protobuf> 项目上的 GrpcServices 属性 – 没有它,会生成客户端和服务器存根,如果只需要一个可能导致构建错误。

参考