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

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.Serialize 和 Serializer.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
应改为:
[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),重写CanWriteResult和WriteResponseBody - 注册时指定
SupportedMediaTypes.Add("application/x-protobuf") - 客户端请求头需带
Accept: application/x-protobuf
别忽略 MIME 类型协商——Protobuf 不是浏览器原生支持的格式,纯前端调用基本不可行,主要用在后端服务间通信(gRPC 除外,那是另一套体系)。
真正容易被忽略的是线程安全性:RuntimeTypeModel.Default 是全局单例,首次访问会触发类型扫描和模型构建,后续并发调用是安全的;但如果你手动创建了多个 RuntimeTypeModel 实例,又没做好初始化同步,可能引发竞态或重复注册异常。










