基础教程中我们学习了 JSON 和 XML 的基本序列化。但真实世界的序列化需求复杂得多:二进制序列化、循环引用处理、自定义转换器、跨语言序列化协议、以及性能敏感的零分配序列化。本文将深入探讨 System.Text.Json、MessagePack、Protobuf 等现代序列化方案的核心原理与实际应用。
System.Text.Json 高级特性 自定义 JsonConverter 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 using System.Text.Json;using System.Text.Json.Serialization;public class CustomDateTimeConverter : JsonConverter <DateTime >{ private readonly string _format; public CustomDateTimeConverter (string format = "yyyy/MM/dd HH:mm:ss" ) { _format = format; } public override DateTime Read (ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options ) { return DateTime.Parse(reader.GetString()!); } public override void Write (Utf8JsonWriter writer, DateTime value , JsonSerializerOptions options ) { writer.WriteStringValue(value .ToString(_format)); } } var options = new JsonSerializerOptions{ Converters = { new CustomDateTimeConverter() } }; var json = JsonSerializer.Serialize(new { Date = DateTime.Now }, options);
条件序列化(ShouldSerialize) 1 2 3 4 5 6 7 8 9 public class Product { public string Name { get ; set ; } = "" ; public decimal Price { get ; set ; } public decimal ? DiscountPrice { get ; set ; } public bool ShouldSerializeDiscountPrice () => DiscountPrice.HasValue; }
JsonIgnore 条件 1 2 3 4 5 6 7 8 9 10 11 public class User { [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull) ] public string ? MiddleName { get ; set ; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault) ] public int DefaultValue { get ; set ; } [JsonInclude ] private string InternalId { get ; set ; } = "" ; }
循环引用处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class Employee { public string Name { get ; set ; } = "" ; public Employee? Manager { get ; set ; } public List<Employee>? Reports { get ; set ; } } var options = new JsonSerializerOptions{ ReferenceHandler = ReferenceHandler.Preserve }; var json = JsonSerializer.Serialize(employee, options);var options2 = new JsonSerializerOptions{ ReferenceHandler = ReferenceHandler.IgnoreCycles };
MessagePack MessagePack 是一种高效的二进制序列化格式,比 JSON 更小更快。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 using MessagePack;[MessagePackObject ] public class Person { [Key(0) ] public string Name { get ; set ; } = "" ; [Key(1) ] public int Age { get ; set ; } [Key(2) ] public string []? Tags { get ; set ; } } var person = new Person { Name = "张三" , Age = 28 , Tags = ["C#" , ".NET" ] };byte [] bytes = MessagePackSerializer.Serialize(person);Person deserialized = MessagePackSerializer.Deserialize<Person>(bytes); MessagePackSerializer.Serialize(person, MessagePackSerializerOptions.Standard .WithCompression(MessagePackCompression.Lz4BlockArray));
性能对比 (非官方基准):
格式
大小(1000 个对象)
序列化时间
反序列化时间
JSON (UTF-8)
~120 KB
~5ms
~6ms
MessagePack
~85 KB
~3ms
~3ms
MessagePack + LZ4
~40 KB
~5ms
~4ms
Protobuf
~60 KB
~2ms
~2ms
Protobuf(Google Protocol Buffers) Protobuf 擅长跨语言数据交换,.NET 中使用 protobuf-net 或 Google.Protobuf。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 using ProtoBuf;[ProtoContract ] public class Order { [ProtoMember(1) ] public int Id { get ; set ; } [ProtoMember(2) ] public string Customer { get ; set ; } = "" ; [ProtoMember(3) ] public decimal Amount { get ; set ; } [ProtoMember(4) ] public List<OrderItem> Items { get ; set ; } = new (); } [ProtoContract ] public class OrderItem { [ProtoMember(1) ] public string ProductName { get ; set ; } = "" ; [ProtoMember(2) ] public int Quantity { get ; set ; } } using var stream = new MemoryStream();Serializer.Serialize(stream, order); stream.Position = 0 ; var deserialized = Serializer.Deserialize<Order>(stream);
自定义序列化 —— ISerializable(.NET 原生) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 [Serializable ] public class ComplexData : ISerializable { public string Name { get ; set ; } public DateTime Timestamp { get ; set ; } public byte []? Payload { get ; set ; } public ComplexData () { Name = "" ; } protected ComplexData (SerializationInfo info, StreamingContext context ) { Name = info.GetString("Name" ) ?? "" ; Timestamp = info.GetDateTime("Timestamp" ); Payload = (byte []?)info.GetValue("Payload" , typeof (byte [])); } public void GetObjectData (SerializationInfo info, StreamingContext context ) { info.AddValue("Name" , Name); info.AddValue("Timestamp" , Timestamp); info.AddValue("Payload" , Payload); } }
序列化性能优化 1. Source Generator(.NET 6+) 1 2 3 4 5 6 7 8 9 10 11 using System.Text.Json.Serialization;[JsonSerializable(typeof(Product[ ]))] [JsonSerializable(typeof(Order)) ] internal partial class AppJsonContext : JsonSerializerContext { } string json = JsonSerializer.Serialize(product, AppJsonContext.Default.Product);var product2 = JsonSerializer.Deserialize(json, AppJsonContext.Default.Product);
2. Utf8JsonWriter——零分配写入 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 using System.Text.Json;byte [] utf8Json;using (var stream = new MemoryStream())using (var writer = new Utf8JsonWriter(stream)){ writer.WriteStartObject(); writer.WriteString("name" , "张三" ); writer.WriteNumber("age" , 28 ); writer.WriteStartArray("tags" ); writer.WriteStringValue("C#" ); writer.WriteStringValue(".NET" ); writer.WriteEndArray(); writer.WriteEndObject(); writer.Flush(); utf8Json = stream.ToArray(); }
3. 管道式反序列化 1 2 3 4 5 6 7 8 9 10 11 using var fs = File.OpenRead("huge_data.json" );var data = await JsonSerializer.DeserializeAsync<Data[]>(fs);using var doc = await JsonDocument.ParseAsync(fs);foreach (var element in doc.RootElement.EnumerateArray()){ var item = JsonSerializer.Deserialize<Data>(element.GetRawText()); Process(item); }
序列化安全 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 string maliciousJson = @"{""$type"":""System.IO.FileInfo, mscorlib""}" ;class SafeBinder : ISerializationBinder { private static readonly HashSet<string > AllowedTypes = new () { "MyApp.Data.Person, MyApp" , "MyApp.Data.Order, MyApp" , }; public Type? BindToType(string ? assemblyName, string typeName) { var fullName = $"{typeName} , {assemblyName} " ; return AllowedTypes.Contains(fullName) ? Type.GetType(fullName) : throw new SecurityException($"类型 {fullName} 不在白名单中" ); } public void BindToName (Type serializedType, out string ? assemblyName, out string ? typeName ) { assemblyName = null ; typeName = null ; } }
跨语言序列化选择指南
协议
适用场景
优点
缺点
JSON
Web API、配置文件
人类可读、广泛支持
体积大、解析慢
MessagePack
.NET 内部 RPC、缓存
体积小、速度快
跨语言支持有限
Protobuf
微服务间 RPC (gRPC)
强类型 Schema、跨语言
需要 .proto 文件
XML
遗留系统、文档格式
Schema 验证、命名空间
臃肿、解析慢
BinaryFormatter
❌ 已废弃
-
不安全、不可移植
总结 :现代 C# 序列化的关键决策在于平衡性能、体积、可读性和跨语言兼容性。System.Text.Json 是 Web API 的首选,MessagePack 适合本地缓存和进程间通信,Protobuf 是 gRPC 微服务的标配。Source Generator 的引入让序列化实现了零反射开销,而自定义转换器和流式 API 则为极端性能场景提供了精细控制。