2016-11-15 30 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