C# protobuf-net使用方法 C#如何使用Protobuf进行高效序列化

8次阅读

protobuf.net序列化必须显式标注[ProtoContract]和[ProtoMember(n)],否则静默失败或抛出InvalidOperationException;仅支持已标记类型,需注意序号唯一性、泛型注册、流位置重置及线程安全。

C# protobuf-net使用方法 C#如何使用Protobuf进行高效序列化

protobuf-net 序列化前必须标记 [ProtoContract][ProtoMember]

不加这些特性,Serializer.Serialize 会静默失败或抛出 InvalidOperationException:“No parameterless constructor defined”——哪怕类有默认构造函数。Protobuf-net 不依赖反射自动发现字段,它只序列化显式标注的成员。

常见错误是只给类加 [ProtoContract],却忘了给每个要序列化的字段/属性加 [ProtoMember(n)],其中 n 是唯一整数序号(不能重复,不能跳空,建议从 1 开始):

[ProtoContract] public class Person {     [ProtoMember(1)] public string Name { get; set; }     [ProtoMember(2)] public int Age { get; set; }     [ProtoMember(3)] public DateTime BirthDate { get; set; } }
  • 序号一旦发布就不要改,否则旧数据反序列化失败
  • 可读写属性、自动属性、私有字段都支持,但字段需加 private + [ProtoMember] 才生效
  • 不支持 dynamic、匿名类型、未标记的嵌套类

Serializer.SerializeSerializer.Deserialize 处理流

这是最轻量、最常用的 API,直接操作 stream,无 jsON 或 xml 中间层,性能接近原生二进制。注意:它不处理编码、长度前缀、分帧,需要自己封装协议头。

典型用法:

var person = new Person { Name = "Alice", Age = 30 }; using var stream = new MemoryStream(); Serializer.Serialize(stream, person); // 写入 stream.position = 0; var deserialized = Serializer.Deserialize(stream); // 读回
  • 务必重置 stream.Position,否则 Deserialize 从末尾开始读,返回默认值
  • 避免对同一 Stream 多次复用而不清空或重置,容易因位置错乱导致反序列化失败
  • 如果要网络传输,推荐配合 SerializeWithLengthPrefix / DeserializeWithLengthPrefix 自动处理变长消息边界

泛型类型和集合要显式告知运行时类型

Protobuf-net 默认不支持开放泛型(如 List)或未实例化的泛型定义。遇到 NotSupportedException: Type is not expected, and no contract can be inferred 就是这个原因。

解决方式有两种:

  • 在类定义中用 [ProtoInclude] 预注册子类型(适合继承场景)
  • 对泛型集合,在序列化前调用 RuntimeTypeModel.default.Add(typeof(List), true) 显式注册
  • 更稳妥的做法:使用具体封闭类型(如 List)并确保其元素类型已标记 [ProtoContract]

例如,以下写法会失败:

[ProtoContract] public class Container {     [ProtoMember(1)] public List Items; // ❌ object 无法推断具体类型 }

应改为:

[ProtoContract] public class Container {     [ProtoMember(1)] public List People; // ✅ 类型明确且已注册 }

避免在 ASP.NET Core 中直接用 Serializer 替换 json 输出

有人试图在 Startup.ConfigureServices 里把 JsonSerializerOptions 换成 protobuf-net 的逻辑,这是行不通的。ASP.NET Core 的 IMvcBuilder.AddJsonOptions 只接受 JsonConverter,不兼容 protobuf-net 的二进制流。

若想全局启用 Protobuf 响应,正确路径是:

  • 实现自定义 OutputFormatter(继承 OutputFormatter),重写 CanWriteResultWriteResponseBody
  • 注册时指定 SupportedMediaTypes.Add("application/x-protobuf")
  • 客户端请求头需带 Accept: application/x-protobuf

别忽略 MIME 类型协商——Protobuf 不是浏览器原生支持的格式,纯前端调用基本不可行,主要用在后端服务间通信(gRPC 除外,那是另一套体系)。

真正容易被忽略的是线程安全性:RuntimeTypeModel.Default 是全局单例,首次访问会触发类型扫描和模型构建,后续并发调用是安全的;但如果你手动创建了多个 RuntimeTypeModel 实例,又没做好初始化同步,可能引发竞态或重复注册异常。

Copyright ©  SEO

 Theme by Puock