serialization
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSerialization in .NET
.NET 中的序列化
When to Use This Skill
何时使用此技能
Use this skill when:
- Choosing a serialization format for APIs, messaging, or persistence
- Migrating from Newtonsoft.Json to System.Text.Json
- Implementing AOT-compatible serialization
- Designing wire formats for distributed systems
- Optimizing serialization performance
在以下场景使用此技能:
- 为API、消息传递或持久化选择序列化格式
- 从Newtonsoft.Json迁移到System.Text.Json
- 实现兼容AOT的序列化
- 为分布式系统设计有线格式
- 优化序列化性能
Schema-Based vs Reflection-Based
基于Schema vs 基于反射
| Aspect | Schema-Based | Reflection-Based |
|---|---|---|
| Examples | Protobuf, MessagePack, System.Text.Json (source gen) | Newtonsoft.Json, BinaryFormatter |
| Type info in payload | No (external schema) | Yes (type names embedded) |
| Versioning | Explicit field numbers/names | Implicit (type structure) |
| Performance | Fast (no reflection) | Slower (runtime reflection) |
| AOT compatible | Yes | No |
| Wire compatibility | Excellent | Poor |
Recommendation: Use schema-based serialization for anything that crosses process boundaries.
| 方面 | 基于Schema的 | 基于反射的 |
|---|---|---|
| 示例 | Protobuf, MessagePack, System.Text.Json (源代码生成) | Newtonsoft.Json, BinaryFormatter |
| 负载中的类型信息 | 无(外部Schema) | 有(嵌入类型名称) |
| 版本控制 | 显式字段编号/名称 | 隐式(类型结构) |
| 性能 | 快速(无反射) | 较慢(运行时反射) |
| AOT兼容性 | 是 | 否 |
| 有线兼容性 | 优秀 | 较差 |
推荐:任何跨进程边界的场景都使用基于Schema的序列化。
Format Recommendations
格式推荐
| Use Case | Recommended Format | Why |
|---|---|---|
| REST APIs | System.Text.Json (source gen) | Standard, AOT-compatible |
| gRPC | Protocol Buffers | Native format, excellent versioning |
| Actor messaging | MessagePack or Protobuf | Compact, fast, version-safe |
| Event sourcing | Protobuf or MessagePack | Must handle old events forever |
| Caching | MessagePack | Compact, fast |
| Configuration | JSON (System.Text.Json) | Human-readable |
| Logging | JSON (System.Text.Json) | Structured, parseable |
| 使用场景 | 推荐格式 | 原因 |
|---|---|---|
| REST API | System.Text.Json (源代码生成) | 标准化、兼容AOT |
| gRPC | Protocol Buffers | 原生格式、版本控制能力优秀 |
| Actor消息传递 | MessagePack 或 Protobuf | 紧凑、快速、版本安全 |
| 事件溯源 | Protobuf 或 MessagePack | 必须永久处理旧事件 |
| 缓存 | MessagePack | 紧凑、快速 |
| 配置 | JSON (System.Text.Json) | 人类可读 |
| 日志 | JSON (System.Text.Json) | 结构化、可解析 |
Formats to Avoid
应避免的格式
| Format | Problem |
|---|---|
| BinaryFormatter | Security vulnerabilities, deprecated, never use |
| Newtonsoft.Json default | Type names in payload break on rename |
| DataContractSerializer | Complex, poor versioning |
| XML | Verbose, slow, complex |
| 格式 | 问题 |
|---|---|
| BinaryFormatter | 存在安全漏洞、已被弃用,绝对不要使用 |
| Newtonsoft.Json 默认配置 | 负载中的类型名称会在重命名时导致失效 |
| DataContractSerializer | 复杂、版本控制能力差 |
| XML | 冗长、缓慢、复杂 |
System.Text.Json with Source Generators
搭配源代码生成器的System.Text.Json
For JSON serialization, use System.Text.Json with source generators for AOT compatibility and performance.
对于JSON序列化,使用搭配源代码生成器的System.Text.Json以实现AOT兼容性和高性能。
Setup
配置
csharp
// Define a JsonSerializerContext with all your types
[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 { }csharp
// 定义包含所有类型的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 { }Usage
使用
csharp
// Serialize with context
var json = JsonSerializer.Serialize(order, AppJsonContext.Default.Order);
// Deserialize with context
var order = JsonSerializer.Deserialize(json, AppJsonContext.Default.Order);
// Configure in ASP.NET Core
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonContext.Default);
});csharp
// 使用上下文进行序列化
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);
});Benefits
优势
- No reflection at runtime - All type info generated at compile time
- AOT compatible - Works with Native AOT publishing
- Faster - No runtime type analysis
- Trim-safe - Linker knows exactly what's needed
- 运行时无反射 - 所有类型信息在编译时生成
- 兼容AOT - 支持Native AOT发布
- 速度更快 - 无需运行时类型分析
- 裁剪安全 - 链接器明确知道所需内容
Protocol Buffers (Protobuf)
Protocol Buffers (Protobuf)
Best for: Actor systems, gRPC, event sourcing, any long-lived wire format.
最适合:Actor系统、gRPC、事件溯源、任何长期使用的有线格式。
Setup
配置
bash
dotnet add package Google.Protobuf
dotnet add package Grpc.Toolsbash
dotnet add package Google.Protobuf
dotnet add package Grpc.ToolsDefine Schema
定义Schema
protobuf
// orders.proto
syntax = "proto3";
message Order {
string id = 1;
string customer_id = 2;
repeated OrderItem items = 3;
int64 created_at_ticks = 4;
// Adding new fields is always safe
string notes = 5; // Added in v2 - old readers ignore it
}
message OrderItem {
string product_id = 1;
int32 quantity = 2;
int64 price_cents = 3;
}protobuf
// 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;
}Versioning Rules
版本控制规则
protobuf
// SAFE: Add new fields with new numbers
message Order {
string id = 1;
string customer_id = 2;
string shipping_address = 5; // NEW - safe
}
// SAFE: Remove fields (old readers ignore unknown, new readers use default)
// Just stop using the field, keep the number reserved
message Order {
string id = 1;
// customer_id removed, but field 2 is reserved
reserved 2;
}
// UNSAFE: Change field types
message Order {
int32 id = 1; // Was: string - BREAKS!
}
// UNSAFE: Reuse field numbers
message Order {
reserved 2;
string new_field = 2; // Reusing 2 - BREAKS!
}protobuf
// 安全操作:使用新编号添加新字段
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
MessagePack
Best for: High-performance scenarios, compact payloads, actor messaging.
最适合:高性能场景、紧凑负载、Actor消息传递。
Setup
配置
bash
dotnet add package MessagePack
dotnet add package MessagePack.Annotationsbash
dotnet add package MessagePack
dotnet add package MessagePack.AnnotationsUsage with Contracts
结合契约使用
csharp
[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; }
// New field - old readers skip unknown keys
[Key(4)]
public string? Notes { get; init; }
}
// Serialize
var bytes = MessagePackSerializer.Serialize(order);
// Deserialize
var order = MessagePackSerializer.Deserialize<Order>(bytes);csharp
[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-Compatible Setup
兼容AOT的配置
csharp
// Use source generator for AOT
[MessagePackObject]
public partial class Order { } // partial enables source gen
// Configure resolver
var options = MessagePackSerializerOptions.Standard
.WithResolver(CompositeResolver.Create(
GeneratedResolver.Instance, // Generated
StandardResolver.Instance));csharp
// 使用源代码生成器实现AOT兼容
[MessagePackObject]
public partial class Order { } // partial关键字启用源代码生成
// 配置解析器
var options = MessagePackSerializerOptions.Standard
.WithResolver(CompositeResolver.Create(
GeneratedResolver.Instance, // 生成的解析器
StandardResolver.Instance));Migrating from Newtonsoft.Json
从Newtonsoft.Json迁移
Common Issues
常见问题
| Newtonsoft | System.Text.Json | Fix |
|---|---|---|
| Not supported by default | Use discriminators or custom converters |
| | Different attribute |
| | Different API |
| | Different API |
| Private setters | Requires | Explicit opt-in |
| Polymorphism | | Explicit discriminators |
| Newtonsoft | System.Text.Json | 修复方案 |
|---|---|---|
| 默认不支持 | 使用鉴别器或自定义转换器 |
| | 不同的特性 |
| | 不同的API |
| | 不同的API |
| 私有setter | 需要 | 显式选择启用 |
| 多态性 | | 显式鉴别器 |
Migration Pattern
迁移模式
csharp
// Newtonsoft (reflection-based)
public class Order
{
[JsonProperty("order_id")]
public string Id { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string? Notes { get; set; }
}
// System.Text.Json (source-gen compatible)
public sealed record Order(
[property: JsonPropertyName("order_id")]
string Id,
string? Notes // Null handling via JsonSerializerOptions
);
[JsonSerializable(typeof(Order))]
[JsonSourceGenerationOptions(
PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
public partial class OrderJsonContext : JsonSerializerContext { }csharp
// 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 { }Polymorphism with Discriminators
搭配鉴别器的多态性
csharp
// .NET 7+ polymorphism
[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);
// Serializes as:
// { "$type": "credit_card", "amount": 100, "last4": "1234" }csharp
// .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" }Wire Compatibility Patterns
有线兼容性模式
Tolerant Reader
容错读取器
Old code must safely ignore unknown fields:
csharp
// Protobuf/MessagePack: Automatic - unknown fields skipped
// System.Text.Json: Configure to allow
var options = new JsonSerializerOptions
{
UnmappedMemberHandling = JsonUnmappedMemberHandling.Skip
};旧代码必须能安全忽略未知字段:
csharp
// Protobuf/MessagePack:自动处理 - 跳过未知字段
// System.Text.Json:配置为允许
var options = new JsonSerializerOptions
{
UnmappedMemberHandling = JsonUnmappedMemberHandling.Skip
};Introduce Read Before Write
先部署读取器再部署写入器
Deploy deserializers before serializers for new formats:
csharp
// Phase 1: Add deserializer (deployed everywhere)
public Order Deserialize(byte[] data, string manifest) => manifest switch
{
"Order.V1" => DeserializeV1(data),
"Order.V2" => DeserializeV2(data), // NEW - can read V2
_ => throw new NotSupportedException()
};
// Phase 2: Enable serializer (next release, after V1 deployed everywhere)
public (byte[] data, string manifest) Serialize(Order order) =>
_useV2Format
? (SerializeV2(order), "Order.V2")
: (SerializeV1(order), "Order.V1");在部署新格式的写入器之前,先部署反序列化器:
csharp
// 阶段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");Never Embed Type Names
绝对不要嵌入类型名称
csharp
// BAD: Type name in payload - renaming class breaks wire format
{
"$type": "MyApp.Order, MyApp.Core",
"id": "123"
}
// GOOD: Explicit discriminator - refactoring safe
{
"type": "order",
"id": "123"
}csharp
// 错误示例:负载中包含类型名称 - 重命名类会破坏有线格式
{
"$type": "MyApp.Order, MyApp.Core",
"id": "123"
}
// 正确示例:显式鉴别器 - 重构安全
{
"type": "order",
"id": "123"
}Performance Comparison
性能对比
Approximate throughput (higher is better):
| Format | Serialize | Deserialize | Size |
|---|---|---|---|
| MessagePack | ★★★★★ | ★★★★★ | ★★★★★ |
| Protobuf | ★★★★★ | ★★★★★ | ★★★★★ |
| System.Text.Json (source gen) | ★★★★☆ | ★★★★☆ | ★★★☆☆ |
| System.Text.Json (reflection) | ★★★☆☆ | ★★★☆☆ | ★★★☆☆ |
| Newtonsoft.Json | ★★☆☆☆ | ★★☆☆☆ | ★★★☆☆ |
For hot paths, prefer MessagePack or Protobuf.
近似吞吐量(数值越高越好):
| 格式 | 序列化 | 反序列化 | 大小 |
|---|---|---|---|
| MessagePack | ★★★★★ | ★★★★★ | ★★★★★ |
| Protobuf | ★★★★★ | ★★★★★ | ★★★★★ |
| System.Text.Json (源代码生成) | ★★★★☆ | ★★★★☆ | ★★★☆☆ |
| System.Text.Json (反射) | ★★★☆☆ | ★★★☆☆ | ★★★☆☆ |
| Newtonsoft.Json | ★★☆☆☆ | ★★☆☆☆ | ★★★☆☆ |
对于热点路径,优先选择MessagePack或Protobuf。
Akka.NET Serialization
Akka.NET 序列化
For Akka.NET actor systems, use schema-based serialization:
hocon
akka {
actor {
serializers {
messagepack = "Akka.Serialization.MessagePackSerializer, Akka.Serialization.MessagePack"
}
serialization-bindings {
"MyApp.Messages.IMessage, MyApp" = messagepack
}
}
}对于Akka.NET Actor系统,使用基于Schema的序列化:
hocon
akka {
actor {
serializers {
messagepack = "Akka.Serialization.MessagePackSerializer, Akka.Serialization.MessagePack"
}
serialization-bindings {
"MyApp.Messages.IMessage, MyApp" = messagepack
}
}
}查看 Akka.NET 序列化文档。
Best Practices
最佳实践
DO
推荐做法
csharp
// Use source generators for System.Text.Json
[JsonSerializable(typeof(Order))]
public partial class AppJsonContext : JsonSerializerContext { }
// Use explicit field numbers/keys
[MessagePackObject]
public class Order
{
[Key(0)] public string Id { get; init; }
}
// Use records for immutable message types
public sealed record OrderCreated(OrderId Id, CustomerId CustomerId);csharp
// 为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);DON'T
不推荐做法
csharp
// Don't use BinaryFormatter (ever)
var formatter = new BinaryFormatter(); // Security risk!
// Don't embed type names in wire format
settings.TypeNameHandling = TypeNameHandling.All; // Breaks on rename!
// Don't use reflection serialization for hot paths
JsonConvert.SerializeObject(order); // Slow, not AOT-compatiblecsharp
// 绝对不要使用BinaryFormatter
var formatter = new BinaryFormatter(); // 安全风险!
// 不要在有线格式中嵌入类型名称
settings.TypeNameHandling = TypeNameHandling.All; // 重命名时会失效!
// 不要在热点路径中使用基于反射的序列化
JsonConvert.SerializeObject(order); // 缓慢、不兼容AOTResources
资源
- System.Text.Json Source Generation: https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/source-generation
- Protocol Buffers: https://protobuf.dev/
- MessagePack-CSharp: https://github.com/MessagePack-CSharp/MessagePack-CSharp
- Akka.NET Serialization: https://getakka.net/articles/networking/serialization.html
- Wire Compatibility: https://getakka.net/community/contributing/wire-compatibility.html
- System.Text.Json 源代码生成: https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/source-generation
- Protocol Buffers: https://protobuf.dev/
- MessagePack-CSharp: https://github.com/MessagePack-CSharp/MessagePack-CSharp
- Akka.NET 序列化: https://getakka.net/articles/networking/serialization.html
- 有线兼容性: https://getakka.net/community/contributing/wire-compatibility.html