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

来源:这里教程网 时间:2026-02-21 17:40:13 作者:

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<Person>(stream); // 读回
务必重置
stream.Position
,否则
Deserialize
从末尾开始读,返回默认值
避免对同一
Stream
多次复用而不清空或重置,容易因位置错乱导致反序列化失败
如果要网络传输,推荐配合
SerializeWithLengthPrefix
/
DeserializeWithLengthPrefix
自动处理变长消息边界

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

Protobuf-net 默认不支持开放泛型(如

List<t></t>
)或未实例化的泛型定义。遇到
NotSupportedException: Type is not expected, and no contract can be inferred
就是这个原因。

解决方式有两种:

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

例如,以下写法会失败:

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

应改为:

[ProtoContract]
public class Container
{
    [ProtoMember(1)] public List<Person> 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
实例,又没做好初始化同步,可能引发竞态或重复注册异常。

相关推荐