使用protobuf-net进行枚举序列化
本文关键字:枚举 序列化 protobuf-net 使用 | 更新日期: 2023-09-27 18:15:42
我在一个大型项目中将protobuf的旧版本更新为当前版本(使用的版本大约有1-2年的历史)。我不知道牧师)。遗憾的是,新版本抛出了一个异常
CreateWireTypeException在ProtoReader.cs行292
在以下测试用例中:
enum Test
{
test1 = 0,
test2
};
static public void Test1()
{
Test original = Test.test2;
using (MemoryStream ms = new MemoryStream())
{
Serializer.SerializeWithLengthPrefix<Test>(ms, original, PrefixStyle.Fixed32, 1);
ms.Position = 0;
Test obj;
obj = Serializer.DeserializeWithLengthPrefix<Test>(ms, PrefixStyle.Fixed32);
}
}
我发现枚举不应该在类之外直接序列化,但是我们的系统太大了,不能简单地将所有枚举包装在类中。这个问题还有别的解决办法吗?只有在DeserializeWithLengthPrefix
抛出异常时,序列化和反序列化才能正常工作。
测试用例在旧版本中工作良好,例如protobuf-net的r262。
很简单,一个bug;这个问题在r640中得到了修复(现在部署到NuGet和google代码中),同时还有一个基于上面代码的额外测试,这样它就不会爬回来了。
Re performance (comments);我要看的第一个提示是:"prefer groups"。基本上,protobuf规范包括两种包含子对象的不同方式——"组"answers"长度-前缀"。Groups是最初的实现,但谷歌现在已经转向"长度前缀",并试图建议人们不要使用"Groups"。然而!由于protobuf-net的工作原理,编写"组"实际上明显更便宜;这是因为不像google的实现, protobuf-net没有提前知道东西的长度。这意味着要编写长度前缀,它需要执行以下操作之一:
- 根据需要计算长度(几乎与实际序列化数据一样多的工作,但要添加一个完整的代码副本);写入长度,然后实际上序列化数据
- 序列化到缓冲区,写入长度,写入缓冲区
- 留下一个占位符,序列化,然后循环并将实际长度写入占位符,如果需要调整填充
我在不同的时间实现了这三种方法,但v2使用了第三种选择。我一直在考虑添加第四个实现:
- 留下一个占位符,序列化,然后循环并使用超长形式写入实际长度(因此不需要填充调整)
但是…共识似乎是,"过长的形式"有点冒险;尽管如此,protobuf-net和protobuf-net之间还是可以很好地工作的。
但是你可以看到:length-prefix总是有一些开销。现在想象一下嵌套相当深的对象,您可以看到一些光点。群体的工作方式非常不同;组的编码格式为:
- 写一个开始标记;序列化;写一个结束标记
就是这样;不需要长度;写起来真的非常非常便宜。在网络上,它们之间的主要区别是:
- 组:写入成本低,但如果遇到意外数据,则不能跳过它们;你必须解析有效负载 的报头
- length-prefix:写入成本更高,但如果遇到意外数据则可以跳过-只需读取长度并复制/移动那么多字节
但是!太多细节了!
这对你意味着什么?假设你有:
[ProtoContract]
public class SomeWrapper
{
[ProtoMember(1)]
public List<Person> People { get { return people; } }
private readonly List<Person> people = new List<Person>();
}
你可以做超级复杂的改变:
[ProtoContract]
public class SomeWrapper
{
[ProtoMember(1, DataFormat=DataFormat.Group)]
public List<Person> People { get { return people; } }
private readonly List<Person> people = new List<Person>();
}
,它将使用更便宜的编码方案。只要您使用protobuf-net,您现有的所有数据都将很好。