protobuf.net 中的继承,添加较低的基类仍然向后兼容? [英] Inheritance in protobuf.net, adding a lower base class still backward compatible?

查看:39
本文介绍了protobuf.net 中的继承,添加较低的基类仍然向后兼容?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在使用 protobuf.net,它非常好.我可以有一个从基类继承的类,我可以通过在基类中使用 ProtoInclude 语句来序列化派生类.如果我的基类在对象序列化时最初只说两个 ProtoInclude 语句,请说

[ProtoInclude(100, typeof(Vol_SurfaceObject))][ProtoInclude(200, typeof(CurveObject))]内部抽象类 MarketDataObject

我仍然可以将同一个对象反序列化为具有更多派生的代码:

[ProtoInclude(100, typeof(Vol_SurfaceObject))][ProtoInclude(200, typeof(CurveObject))][ProtoInclude(300, typeof(StaticDataObject))]内部抽象类 MarketDataObject

到目前为止一切都很好(实际上非​​常好,谢谢 Marc).但是,现在如果我想要一个基类低于我当前的基类(在本例中为 MarketDataObject),该怎么办?这样我就有

[ProtoInclude(100, typeof(Vol_SurfaceObject))][ProtoInclude(200, typeof(CurveObject))][ProtoInclude(300, typeof(StaticDataObject))]内部抽象类 MarketDataObject : LowerStillBaseClass{废话}[ProtoInclude(10, typeof(MarketDataObject))]内部抽象类 LowerStillBaseClass{废话}

虽然代码当然可以工作,但当对象只有 2 个 ProtoInclude 语句到这种新形式的 MarketDataObject 类时,我是否仍然能够反序列化已序列化的初始对象?

解决方案

这不会仅适用于静态 protbuf-net 属性.稍微简化一下,假设您从以下内容开始:

命名空间 V1{[原合同]内部类 MarketDataObject{[ProtoMember(1)]公共字符串 ID { 获取;放;}}}

并将其重构为以下内容:

命名空间 V2{[ProtoInclude(10, typeof(MarketDataObject))][原合同]内部抽象类 LowerStillBaseClass{[ProtoMember(1)]公共字符串 LowerStillBaseClassProperty { 获取;放;}}[原合同]内部类 MarketDataObject : LowerStillBaseClass{[ProtoMember(1)]公共字符串 ID { 获取;放;}}}

接下来,尝试将 V1 类创建的 a 反序列化为 V2 类.您将失败并出现以下异常:

<块引用>

ProtoBuf.ProtoException: 没有为 LowerStillBaseClass 找到无参数构造函数

原因这是行不通的,因为类型层次结构是序列化的基础优先而不是派生优先.为了看到这一点,通过调用 Console.WriteLine(RuntimeTypeModel.Default.GetSchema(type)); 为每种类型转储 protobuf-net 合约,对于 V1.MarketDataObject 我们得到:

message MarketDataObject {可选字符串 Id = 1;}

对于V2.MarketDataObject:

message LowerStillBaseClass {可选字符串 LowerStillBaseClassProperty = 1;//以下代表子类型;最多 1 应该有一个值可选 MarketDataObject MarketDataObject = 10;}消息市场数据对象 {可选字符串 Id = 1;}

MarketDataObject 被编码为消息 首先在顶层使用其基本类型字段,然后将派生类型字段递归封装在嵌套的可选消息中,该消息具有表示其子类型的字段 id.所以当 V1 消息反序列化为 V2 对象时,没有遇到子类型字段,没有推断出正确的派生类型,并且派生类型值丢失.

一种解决方法是避免使用 [ProtoInclude(10, typeof(MarketDataObject))],而是使用 [ProtoInclude(10, typeof(MarketDataObject))] 以编程方式填充派生类型契约中的基类成员a href="http://www.codeproject.com/Articles/642677/Protobuf-net-the-unofficial-manual#without" rel="nofollow noreferrer">RuntimeTypeModel API:

命名空间 V3{[原合同]内部抽象类 LowerStillBaseClass{[ProtoMember(1)]公共字符串 LowerStillBaseClassProperty { 获取;放;}}[原合同]内部类 MarketDataObject : LowerStillBaseClass{静态 MarketDataObject(){AddBaseTypeProtoMembers(RuntimeTypeModel.Default);}const int BaseTypeIncrement = 11000;public static void AddBaseTypeProtoMembers(RuntimeTypeModel runtimeTypeModel){var myType = runtimeTypeModel[typeof(MarketDataObject)];var baseType = runtimeTypeModel[typeof(MarketDataObject).BaseType];if (!baseType.GetSubtypes().Any(s => s.DerivedType == myType)){foreach(baseType.GetFields() 中的 var 字段){myType.Add(field.FieldNumber + BaseTypeIncrement, field.Name);}}}[ProtoMember(1)]公共字符串 ID { 获取;放;}}}

(在这里,我在 MarketDataObject 的静态构造函数中填充合约.您可能想在其他地方执行此操作.)V3. 的架构如下所示:>

message MarketDataObject {可选字符串 Id = 1;可选字符串 LowerStillBaseClassProperty = 11001;}

此模式与 V1 模式兼容,因此可以将 V1 消息反序列化为 V3 类而不会丢失数据.示例 fiddle.

当然,如果您将成员从 MarketDataObject 移动到 LowerStillBaseClass,您需要确保字段 ID 保持不变.

这种变通方法的缺点是您无法反序列化 LowerStillBaseClass 类型的对象,并使 protobuf-net 自动推断正确的派生类型.

I have been using protobuf.net for a while and it is excellent. I can have a class which is inherited from a base class, I can serialise the derived class by using ProtoInclude statements in the base class. If my base class originally had only say two ProtoInclude statements when the object was serialised, say

[ProtoInclude(100, typeof(Vol_SurfaceObject))]
[ProtoInclude(200, typeof(CurveObject))]
internal abstract class MarketDataObject 

I can still deserialise that same object in to code that has evolved to have more derivations:

[ProtoInclude(100, typeof(Vol_SurfaceObject))]
[ProtoInclude(200, typeof(CurveObject))]
[ProtoInclude(300, typeof(StaticDataObject))]
internal abstract class MarketDataObject 

So far so good (in fact excellent, thanks Marc). However, now what if I want to have a base class even lower then my current base class here (in this case, MarketDataObject). Such that I would have

[ProtoInclude(100, typeof(Vol_SurfaceObject))]
[ProtoInclude(200, typeof(CurveObject))]
[ProtoInclude(300, typeof(StaticDataObject))]
internal abstract class MarketDataObject : LowerStillBaseClass
{ blah }

[ProtoInclude(10, typeof(MarketDataObject))]
internal abstract class LowerStillBaseClass
{ blah }

Whilst the code will of course work, will I be still be able to deserialise the initial objects that were serialised when the object had only 2 ProtoInclude statements to this new form of the MarketDataObject class?

解决方案

This will not work purely with static protbuf-net attributes. Simplifying somewhat, imagine you start with the following :

namespace V1
{
    [ProtoContract]
    internal class MarketDataObject
    {
        [ProtoMember(1)]
        public string Id { get; set; }
    }
}

And refactor it to be the following:

namespace V2
{
    [ProtoInclude(10, typeof(MarketDataObject))]
    [ProtoContract]
    internal abstract class LowerStillBaseClass
    {
        [ProtoMember(1)]
        public string LowerStillBaseClassProperty { get; set; }
    }

    [ProtoContract]
    internal class MarketDataObject : LowerStillBaseClass
    {
        [ProtoMember(1)]
        public string Id { get; set; }
    }
}

Next, try to deserialize a created from the V1 class into a V2 class. You will fail with the following exception:

ProtoBuf.ProtoException: No parameterless constructor found for LowerStillBaseClass

The reason this does not work is that type hierarchies are serialized base-first rather than derived-first. To see this, dump the protobuf-net contracts for each type by calling Console.WriteLine(RuntimeTypeModel.Default.GetSchema(type)); For V1.MarketDataObject we get:

message MarketDataObject {
   optional string Id = 1;
}

And for V2.MarketDataObject:

message LowerStillBaseClass {
   optional string LowerStillBaseClassProperty = 1;
   // the following represent sub-types; at most 1 should have a value
   optional MarketDataObject MarketDataObject = 10;
}
message MarketDataObject {
   optional string Id = 1;
}

MarketDataObject is getting encoded into a message with its base type fields first, at the top level, then derived type fields are recursively encapsulated inside a nested optional message with a field id that represents its subtype. So when a V1 message is deserialized to a V2 object, no subtype field is encountered, the correct derived type is not inferred, and derived type values are lost.

One workaround is to avoid using [ProtoInclude(10, typeof(MarketDataObject))] and instead populate the base class members in the derived type's contract programmatically using the RuntimeTypeModel API:

namespace V3
{
    [ProtoContract]
    internal abstract class LowerStillBaseClass
    {
        [ProtoMember(1)]
        public string LowerStillBaseClassProperty { get; set; }
    }

    [ProtoContract]
    internal class MarketDataObject : LowerStillBaseClass
    {
        static MarketDataObject()
        {
            AddBaseTypeProtoMembers(RuntimeTypeModel.Default);
        }

        const int BaseTypeIncrement = 11000;

        public static void AddBaseTypeProtoMembers(RuntimeTypeModel runtimeTypeModel)
        {
            var myType = runtimeTypeModel[typeof(MarketDataObject)];
            var baseType = runtimeTypeModel[typeof(MarketDataObject).BaseType];
            if (!baseType.GetSubtypes().Any(s => s.DerivedType == myType))
            {
                foreach (var field in baseType.GetFields())
                {
                    myType.Add(field.FieldNumber + BaseTypeIncrement, field.Name);
                }
            }
        }

        [ProtoMember(1)]
        public string Id { get; set; }
    }
}

(Here I am populating the contract inside the static constructor for MarketDataObject. You might want to do it elsewhere.) The schema for V3. looks like:

message MarketDataObject {
   optional string Id = 1;
   optional string LowerStillBaseClassProperty = 11001;
}

This schema is compatible with the V1 schema, and so A V1 message can be deserialized into a V3 class without data loss. Sample fiddle.

Of course, if you are moving a member from MarketDataObject to LowerStillBaseClass you will need to ensure that the field id stays the same.

The disadvantage of this workaround is that you lose the ability to deserialize an object of type LowerStillBaseClass and have protobuf-net automatically infer the correct derived type.

这篇关于protobuf.net 中的继承,添加较低的基类仍然向后兼容?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆