用 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 调用除外)吗? ProtoReaderProtoWriter 非常适合 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;

其中12 将成为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 - 啥被序列化了?

使用 Protobuf .net 反序列化单个结构字段

使用 protobuf-net.Grpc 的客户端回调

protobuf-net 出现“已添加具有相同密钥的项目”错误

protobuf 下载编译,拷贝,proto文件生成c# c++代码

protobuf 下载编译,拷贝,proto文件生成c# c++代码