protobuf-net 中的对象继承

Posted

技术标签:

【中文标题】protobuf-net 中的对象继承【英文标题】:Object inheritance in protobuf-net 【发布时间】:2014-07-23 08:45:58 【问题描述】:

考虑这些类定义:

[ProtoContract, ProtoInclude(2, typeof(Class2))]
class Class1

    [ProtoMember(1)]
    public string Field1  get; set; 


[ProtoContract]
class Class2 : Class1

    [ProtoMember(1)]
    public string Field2  get; set; 

我正在努力实现以下目标:

using (var ms = new MemoryStream())

    var c1 = new Class1  Field1 = "hello" ;
    Serializer.Serialize<Class1>(ms, c1);

    ms.Position = 0;

    var c2 = Serializer.Deserialize<Class2>(ms);

但我得到以下异常:Unable to cast object of type 'ProtoBufTest.Class1' to type 'ProtoBufTest.Class2'

我真的不明白这个问题;我的理解是,在反序列化时,Protobuf 应该只将传入的流视为字节的集合,那么为什么它显然首先反序列化为 Class1 对象,然后尝试将其放入 Class2 中?

【问题讨论】:

【参考方案1】:

通过添加[ProtoInclude(...)],您告诉protobuf-net 以允许继承工作的方式处理Class1Class2。无论您指定&lt;Class1&gt; 还是&lt;Class2&gt;,protobuf-net 都将从基础类型开始向上构建;基本上你的模型已经变成了(在 protobuf 中):

message Class1 
   optional string Field1 = 1;
   // the following represent sub-types; at most 1 should have a value
   optional Class2 Class2 = 2;

message Class2 
   optional string Field2 = 1;

如果存在.Class2 实例,它将反序列化为Class2;否则它将反序列化为Class1。这是有意,因此如果您序列化Class1,您将返回一个Class1,如果您序列化一个Class2,您将返回一个Class2

如果要分别考虑这两种类型,不要添加[ProtoInclude]。事实上,在这种情况下,您甚至可以使用Serializer.ChangeType 来进行序列化/反序列化往返:

var c1 = new Class1  Field1 = "hello" ;
var c2 = Serializer.ChangeType<Class1, Class2>(c1);

注意:在那种情况下,我想知道为什么首先存在继承关系。从你正在做的事情来看,感觉就像你实际上只是想要:

[ProtoContract]
class Class1

    [ProtoMember(1)]
    public string Field1  get; set; 


[ProtoContract]
class Class2

    [ProtoMember(1)]
    public string Field2  get; set; 

(虽然我不知道为什么

【讨论】:

感谢您的详尽解释。实际上,我真的不需要 Class2 中的任何 ProtoMember,因为我的用例是反序列化 Class1 并在将其发送到客户端之前使用其他字段对其进行扩充(对象中的字段“平面”用于 JSON 序列化)。我试过 Serializer.ChangeType 但结果中 Field1 和 Field2 都为空。 @ThomasWeiss 在这里工作得很好......需要看看它不工作的例子 通过使用我的帖子中没有 ProtoInclude 的类定义,并调用 c2 = Serializer.ChangeType(c1),我得到 c2.Field1 = null 和 c2.Field2 = "hello "。【参考方案2】:

因为您发送的是 Class1 的实例,而不是 Class2。 如果你用 Class1 的实例调用方法,你不能神奇地将它转换为 Class2。

您可以创建 Class2 的新实例并由 Class1 实例中的成员填充。 但它看起来像糟糕的设计。

【讨论】:

调用 Serializer.Deserialize(ms) 时,Serializer 接收的唯一内容是一个不引用 Class1 的流(类型名称未序列化 AFAIK),那么为什么'Class1' 在这里弹出? 根据***.com/questions/947666/…,当您使用 ProtoInclude 属性时,将包含类型的元信息。 我从这个链接了解到的是,ProtoInclude 为子类的内容插入了一个“占位符”,但是没有嵌入实际的类型信息。所以看起来当从 Class1 反序列化流时,Protobuf 发现这个占位符是空的,所以它猜测它应该被反序列化为 Class1,而不是 Class2。我期待获得 Class2,其特定字段为空/默认...

以上是关于protobuf-net 中的对象继承的主要内容,如果未能解决你的问题,请参考以下文章

Protobuf-net 中的继承:ProtoInclude 和兼容性

使用protobuf-net继承时如何选择字段号?

关于继承的 ProtoBuf-Net 的问题?

ProtoBuf-Net ProtoInclude 泛型类型子类

Protobuf-net.Grpc 服务契约继承

如何停止使用 Protobuf-Net 继承,直接使用继承类?