用 protobuf 替换 binaryformatter
Posted
技术标签:
【中文标题】用 protobuf 替换 binaryformatter【英文标题】:Replace binaryformatter by protobuf 【发布时间】:2014-05-12 22:44:46 【问题描述】:我需要替换将数据存储在文件中的库(序列化/反序列化) 这个库目前使用 BinaryFormatter,但对于大型列表来说速度很慢。 *** 上的许多帖子都表明 protobuf 非常高效,所以我正在尝试使用它。
为了在不重写大量代码的情况下进行替换,我的要求是:
必须序列化通用数据,因为我必须与Store<T>(T data)
交互(大部分时间 T 标记有 DataContract 和/或 Serializable 属性)
我无法修改所有不同的对象类来添加 protobuf 属性,所以我需要一个不使用这些属性的通用方法
我不需要向后兼容:如果 T 对象架构更改(即新属性),所有生成的文件都已过时,将被删除/重新创建
我的第一个幼稚的实现如下所示:
public bool Store<T>(string key, T data)
var formatter = Serializer.CreateFormatter<T>();
using (var fileStream = new FileStream(this.GetFilePath(key), FileMode.Create))
formatter.Serialize(fileStream, data);
return true;
但后来我得到了这个例外:
类型不是预期的,也不能推断出任何契约: My.Application.Namespace.ShortcutData
目前我有点卡住了,我没有找到关于如何使用 protobuf-net 的好教程。
是否可以使用 protobuf 达到这些要求?你有关于如何做到这一点的好教程吗?
编辑:
问题确实是我需要告诉 protobuf 如何(反)序列化数据。 这是我现在拥有的:
public bool Store<T>(string key, T data)
this.Register<T>();
var formatter = Serializer.CreateFormatter<T>();
using (var fileStream = new FileStream(this.GetFilePath(key), FileMode.Create))
formatter.Serialize(fileStream, data);
fileStream.Close();
return true;
主要代码在Register方法中:
protected void Register(Type type)
if (type.IsGenericType)
var arguments = type.GetGenericArguments();
foreach (var argument in arguments)
this.Register(argument);
if (!this._registeredTypes.Contains(type) && !type.IsValueType)
this._registeredTypes.Add(type);
var properties = type.GetProperties();
foreach (var property in properties)
this.Register(property.PropertyType);
try
ProtoBuf.Meta.RuntimeTypeModel.Default
.Add(type, false)
.Add(properties
.Where( p => p.CanWrite)
.OrderBy(x => x.Name)
.Select(x => x.Name)
.ToArray());
catch
// I've a problem here: I sometime have an error for an already registered type (??)
我知道这不是一个干净的代码,但这只是对现有代码的替代,并且将在第二步中考虑到 protobuf 的完全重写。
【问题讨论】:
【参考方案1】:我才刚刚开始使用 Protobuf-net.. 所以请不要以为我的回答是“正确”的做事方式。毫无疑问,Marc Gravell 会在某些时候为您提供答案,您一定要注意。
..但是,我前几天写的概念证明要求我快速获得大量序列化的类。为此..我想出了这个:
var assembly = Assembly.Load("Assembly.Name.Here");
foreach (var type in assembly.GetTypes().Where(x => typeof (IInterfaceEachClassImplements).IsAssignableFrom(x)))
RuntimeTypeModel
.Default
.Add(type, false)
.Add(type.GetProperties()
.Select(x => x.Name)
.ToArray());
基本上,它基于每个类都实现的接口将每个类加载到 protobuf-net 类型模型中......而不是应用于它们的属性。
希望这无论如何都能为您指明正确的方向。
【讨论】:
该代码不可靠且脆弱:GetProperties()
不保证顺序,并且顺序在这里真的很重要,因为它决定了隐含的字段编号。
我有点想.. 但正如我所说:概念证明。如果我要订购这些属性会有所不同吗?我的实际代码现在倾向于 ProtoReader 和 ProtoWriter 以更好地控制我特别需要编写的内容。你有什么建议让它不那么脆弱(GetProperties 调用除外)吗?
ProtoReader
和 ProtoWriter
非常适合 protobuf-net 内部的需求;您当然可以尝试直接使用它们,但这不是常见的用例。重新使它不那么脆弱:坦率地说,我会使用属性方法,它更简单。如果您要序列化的类型不是您的:使用序列化代理,或为序列化创建单独的 DTO 模型。
你有一个代理如何工作的例子吗?我的用例是我不拥有的类型。对不起..我意识到我正在把这个答案变成自己的问题......
确定;创建一个 protobuf-net 喜欢的单独类型,它们使用model.Add(typeof(AwkwardType), false).SetSurrogate(typeof(DtoType);
。请注意,这两种类型中的一种必须包含与另一种类型之间的转换运算符。【参考方案2】:
ShortcutData
是什么? protobuf 格式不包含任何类型元数据,因此引擎有必要了解如何映射数据。这可以通过多种方式完成,包括通过各种属性。该映射也可以在运行时通过涉及RuntimeTypeModel
的代码指定。例如,如果我们的ShortcutData
看起来像:
public class ShortcutData
public int Key get;set;
public string Link get;set;
那么我们可以简单地这样做:
[ProtoContract]
public class ShortcutData
[ProtoMember(1)]
public int Key get;set;
[ProtoMember(2)]
public string Link get;set;
其中1
和2
将成为protobuf 字段标识符。要序列化,您不需要使用格式化程序 API。简单地说:
public bool Store<T>(string key, T data)
using (var fileStream = File.Create(GetFilePath(key)))
Serializer.Serialize<T>(fileStream, data);
return true;
【讨论】:
ShortcutData 是一个 DTO。问题是我们有 1000 多个 DTO,所以我无法在给定时间内更新所有 DTO。这将是第二步。所以我尝试在没有任何属性和代理的情况下这样做。以上是关于用 protobuf 替换 binaryformatter的主要内容,如果未能解决你的问题,请参考以下文章
protobuf-net 出现“已添加具有相同密钥的项目”错误