.NET序列化格式选择与最佳实践Skill serialization

本技能是关于.NET应用程序中序列化技术的专业指南。它详细对比了基于模式的序列化(如Protobuf、MessagePack)与基于反射的序列化(如Newtonsoft.Json)的优劣,并针对不同应用场景(如REST API、gRPC、事件溯源、缓存等)提供了明确的格式推荐。核心内容包括:如何使用System.Text.Json的源生成器实现AOT兼容和高性能JSON序列化;Protocol Buffers和MessagePack的详细配置、使用及版本控制规则;从Newtonsoft.Json迁移到System.Text.Json的常见问题与解决方案;确保有线兼容性的设计模式;以及性能对比和最佳实践。关键词:.NET序列化、Protocol Buffers、MessagePack、System.Text.Json、AOT兼容、源生成、高性能序列化、版本控制、有线格式、Newtonsoft.Json迁移。

后端开发 0 次安装 0 次浏览 更新于 2/26/2026

名称: 序列化 描述: 为.NET应用程序选择合适的序列化格式。优先选择基于模式的格式(Protobuf、MessagePack),而非基于反射的格式(Newtonsoft.Json)。对于JSON场景,使用带有AOT源生成器的System.Text.Json。 可调用: false

.NET中的序列化

何时使用此技能

在以下情况下使用此技能:

  • 为API、消息传递或持久化选择序列化格式时
  • 从Newtonsoft.Json迁移到System.Text.Json时
  • 实现AOT兼容的序列化时
  • 为分布式系统设计有线格式时
  • 优化序列化性能时

基于模式 vs 基于反射

方面 基于模式 基于反射
示例 Protobuf、MessagePack、System.Text.Json(源生成) Newtonsoft.Json、BinaryFormatter
有效载荷中的类型信息 无(外部模式) 有(嵌入类型名称)
版本控制 显式字段编号/名称 隐式(类型结构)
性能 快(无反射) 较慢(运行时反射)
AOT兼容
有线兼容性 优秀

建议:对于任何跨越进程边界的情况,使用基于模式的序列化。


格式推荐

使用场景 推荐格式 原因
REST API System.Text.Json(源生成) 标准,AOT兼容
gRPC Protocol Buffers 原生格式,版本控制优秀
Actor消息传递 MessagePack 或 Protobuf 紧凑,快速,版本安全
事件溯源 Protobuf 或 MessagePack 必须永久处理旧事件
缓存 MessagePack 紧凑,快速
配置 JSON(System.Text.Json) 人类可读
日志记录 JSON(System.Text.Json) 结构化,可解析

应避免的格式

格式 问题
BinaryFormatter 安全漏洞,已弃用,切勿使用
Newtonsoft.Json默认 有效载荷中的类型名称在重命名时会破坏
DataContractSerializer 复杂,版本控制差
XML 冗长,慢,复杂

使用源生成器的System.Text.Json

对于JSON序列化,使用带有源生成器的System.Text.Json以实现AOT兼容性和性能。

设置

// 定义一个包含所有类型的JsonSerializerContext
[JsonSerializable(typeof(Order))]
[JsonSerializable(typeof(OrderItem))]
[JsonSerializable(typeof(Customer))]
[JsonSerializable(typeof(List<Order>))]
[JsonSourceGenerationOptions(
    PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
public partial class AppJsonContext : JsonSerializerContext { }

用法

// 使用上下文序列化
var json = JsonSerializer.Serialize(order, AppJsonContext.Default.Order);

// 使用上下文反序列化
var order = JsonSerializer.Deserialize(json, AppJsonContext.Default.Order);

// 在ASP.NET Core中配置
builder.Services.ConfigureHttpJsonOptions(options =>
{
    options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonContext.Default);
});

优势

  • 运行时无反射 - 所有类型信息在编译时生成
  • AOT兼容 - 适用于Native AOT发布
  • 更快 - 无需运行时类型分析
  • 修剪安全 - 链接器确切知道需要什么

Protocol Buffers (Protobuf)

最佳适用:Actor系统、gRPC、事件溯源、任何长期存在的有线格式。

设置

dotnet add package Google.Protobuf
dotnet add package Grpc.Tools

定义模式

// orders.proto
syntax = "proto3";

message Order {
    string id = 1;
    string customer_id = 2;
    repeated OrderItem items = 3;
    int64 created_at_ticks = 4;

    // 添加新字段总是安全的
    string notes = 5;  // 在v2中添加 - 旧读取器忽略它
}

message OrderItem {
    string product_id = 1;
    int32 quantity = 2;
    int64 price_cents = 3;
}

版本控制规则

// 安全:使用新编号添加新字段
message Order {
    string id = 1;
    string customer_id = 2;
    string shipping_address = 5;  // 新增 - 安全
}

// 安全:移除字段(旧读取器忽略未知字段,新读取器使用默认值)
// 只需停止使用该字段,保留编号为保留
message Order {
    string id = 1;
    // customer_id 已移除,但字段2被保留
    reserved 2;
}

// 不安全:更改字段类型
message Order {
    int32 id = 1;  // 原来是:string - 会破坏!
}

// 不安全:重用字段编号
message Order {
    reserved 2;
    string new_field = 2;  // 重用2 - 会破坏!
}

MessagePack

最佳适用:高性能场景、紧凑有效载荷、Actor消息传递。

设置

dotnet add package MessagePack
dotnet add package MessagePack.Annotations

使用合约的用法

[MessagePackObject]
public sealed class Order
{
    [Key(0)]
    public required string Id { get; init; }

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

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

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

    // 新字段 - 旧读取器跳过未知键
    [Key(4)]
    public string? Notes { get; init; }
}

// 序列化
var bytes = MessagePackSerializer.Serialize(order);

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

AOT兼容设置

// 为AOT使用源生成器
[MessagePackObject]
public partial class Order { }  // partial 启用源生成

// 配置解析器
var options = MessagePackSerializerOptions.Standard
    .WithResolver(CompositeResolver.Create(
        GeneratedResolver.Instance,  // 生成的
        StandardResolver.Instance));

从Newtonsoft.Json迁移

常见问题

Newtonsoft System.Text.Json 修复方法
JSON中的 $type 默认不支持 使用鉴别器或自定义转换器
JsonProperty JsonPropertyName 不同的属性
DefaultValueHandling DefaultIgnoreCondition 不同的API
NullValueHandling DefaultIgnoreCondition 不同的API
私有setter 需要 [JsonInclude] 显式选择加入
多态性 [JsonDerivedType] (.NET 7+) 显式鉴别器

迁移模式

// Newtonsoft(基于反射)
public class Order
{
    [JsonProperty("order_id")]
    public string Id { get; set; }

    [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
    public string? Notes { get; set; }
}

// System.Text.Json(源生成兼容)
public sealed record Order(
    [property: JsonPropertyName("order_id")]
    string Id,

    string? Notes  // 通过JsonSerializerOptions处理空值
);

[JsonSerializable(typeof(Order))]
[JsonSourceGenerationOptions(
    PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower,
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
public partial class OrderJsonContext : JsonSerializerContext { }

使用鉴别器的多态性

// .NET 7+ 多态性
[JsonDerivedType(typeof(CreditCardPayment), "credit_card")]
[JsonDerivedType(typeof(BankTransferPayment), "bank_transfer")]
public abstract record Payment(decimal Amount);

public sealed record CreditCardPayment(decimal Amount, string Last4) : Payment(Amount);
public sealed record BankTransferPayment(decimal Amount, string AccountNumber) : Payment(Amount);

// 序列化为:
// { "$type": "credit_card", "amount": 100, "last4": "1234" }

有线兼容性模式

容错读取器

旧代码必须安全地忽略未知字段:

// Protobuf/MessagePack:自动 - 跳过未知字段
// System.Text.Json:配置为允许
var options = new JsonSerializerOptions
{
    UnmappedMemberHandling = JsonUnmappedMemberHandling.Skip
};

先读后写

在部署新格式的序列化器之前先部署反序列化器:

// 阶段1:添加反序列化器(部署到各处)
public Order Deserialize(byte[] data, string manifest) => manifest switch
{
    "Order.V1" => DeserializeV1(data),
    "Order.V2" => DeserializeV2(data),  // 新增 - 可以读取V2
    _ => throw new NotSupportedException()
};

// 阶段2:启用序列化器(下一个版本,在V1部署到各处之后)
public (byte[] data, string manifest) Serialize(Order order) =>
    _useV2Format
        ? (SerializeV2(order), "Order.V2")
        : (SerializeV1(order), "Order.V1");

切勿嵌入类型名称

// 错误:有效载荷中的类型名称 - 重命名类会破坏有线格式
{
    "$type": "MyApp.Order, MyApp.Core",
    "id": "123"
}

// 正确:显式鉴别器 - 重构安全
{
    "type": "order",
    "id": "123"
}

性能比较

近似吞吐量(越高越好):

格式 序列化 反序列化 大小
MessagePack ★★★★★ ★★★★★ ★★★★★
Protobuf ★★★★★ ★★★★★ ★★★★★
System.Text.Json(源生成) ★★★★☆ ★★★★☆ ★★★☆☆
System.Text.Json(反射) ★★★☆☆ ★★★☆☆ ★★★☆☆
Newtonsoft.Json ★★☆☆☆ ★★☆☆☆ ★★★☆☆

对于热点路径,优先选择MessagePack或Protobuf。


Akka.NET序列化

对于Akka.NET actor系统,使用基于模式的序列化:

akka {
  actor {
    serializers {
      messagepack = "Akka.Serialization.MessagePackSerializer, Akka.Serialization.MessagePack"
    }
    serialization-bindings {
      "MyApp.Messages.IMessage, MyApp" = messagepack
    }
  }
}

参见 Akka.NET序列化文档


最佳实践

应该做

// 为System.Text.Json使用源生成器
[JsonSerializable(typeof(Order))]
public partial class AppJsonContext : JsonSerializerContext { }

// 使用显式字段编号/键
[MessagePackObject]
public class Order
{
    [Key(0)] public string Id { get; init; }
}

// 对不可变消息类型使用记录
public sealed record OrderCreated(OrderId Id, CustomerId CustomerId);

不应该做

// 不要使用BinaryFormatter(永远不要)
var formatter = new BinaryFormatter();  // 安全风险!

// 不要在有线格式中嵌入类型名称
settings.TypeNameHandling = TypeNameHandling.All;  // 重命名时会破坏!

// 不要对热点路径使用反射序列化
JsonConvert.SerializeObject(order);  // 慢,不兼容AOT

资源