2016-11-15 29 views
1

我一直在使用protobuf.net一段时间,它非常出色。我可以有一个从基类继承的类,我可以通过在基类中使用ProtoInclude语句来序列化派生类。如果我的基类原本只说两个ProtoInclude报表时,该对象被序列化,说继承protobuf.net,添加一个较低的基类仍然向后兼容?

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

我仍然可以deserialise,在代码相同的对象已经发展到有更多的推导:

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

到目前为止这么好(其实非常好,谢谢Marc)。然而,现在如果我想让一个基类甚至低于我目前的基类(在这种情况下,MarketDataObject),现在该怎么办?这样,我将不得不

[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 } 

虽然课程作业的代码,我是否会仍然能够deserialise这是序列化对象时只有2 ProtoInclude语句到MarketDataObject类的这种新形式的初始对象?

回答

1

这不会纯粹与静态protbuf-net属性一起工作。有些简化,假设你开始了以下内容:

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

并重构它是以下几点:

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; } 
    } 
} 

接下来,尝试反序列化从V1类创建成V2类。你会失败,但以下情况除外:

ProtoBuf.ProtoException: No parameterless constructor found for LowerStillBaseClass 

这不工作的原因是类型层次是串行化的基本优先,而非派生第一。看到这一点,通过调用Console.WriteLine(RuntimeTypeModel.Default.GetSchema(type));对于V1.MarketDataObject转储每种类型的protobuf网合同,我们得到:

message MarketDataObject { 
    optional string Id = 1; 
} 

并为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是越来越编码为message,其基本类型字段首先在顶层,然后派生类型字段被递归地封装在一个嵌套的可选消息中,字段ID代表它的子类型。因此,当V1消息被反序列化为V2对象时,不会遇到子类型字段,不会推断正确的派生类型,并且派生类型值将丢失。

一个解决办法是避免使用[ProtoInclude(10, typeof(MarketDataObject))],而是以编程方式使用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; } 
    } 
} 

(我在这里填充静态构造函数中的合同MarketDataObject你可能想在别处做。)为V3.的模式是这样的:

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

这个模式是与V1模式兼容,所以甲V1消息可以被反序列化为无数据丢失一个V3类。样品fiddle

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

此变通办法的缺点是,您将失去反序列化LowerStillBaseClass类型对象的能力,并让protobuf-net自动推断出正确的派生类型。

+0

谢谢你非常详细的答案和它一定带你的时间。你说的很完美。 – screig