2016-11-15 5 views
1

私はしばらくの間protobuf.netを使用しており、それは優れています。私は基本クラスから継承されたクラスを持つことができます、私は基本クラスでProtoIncludeステートメントを使用して派生クラスをserialiseすることができます。protobuf.netの継承で、下位互換のベースクラスを追加しましたか?

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

:オブジェクトがシリアライズされたときに、私の基本クラスは、もともと2つのだけProtoInclude文を言っていたら、私はまだ進化しているコードへの同じオブジェクトがより多くの派生を持っていることをdeserialiseでき

[ProtoInclude(100, typeof(Vol_SurfaceObject))] 
[ProtoInclude(200, typeof(CurveObject))] 
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 } 

を持つことになり、私はまだオブジェクトがMarketDataObjectクラスのこの新しいフォームにのみ2 ProtoInclude文を持っていたときシリアライズされた最初のオブジェクトをdeserialiseすることができるようになるように?

答えて

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

次に、V2クラスにV1クラスから作成されたデシリアライズしてみてください。あなたは、次の例外で失敗します:

ProtoBuf.ProtoException: No parameterless constructor found for LowerStillBaseClass 

これが動作しない理由は、そのタイプ階層がシリアライズされたベース - 最初のではなく、派生最初のものであるです。これを確認するには、V1.MarketDataObjectについてConsole.WriteLine(RuntimeTypeModel.Default.GetSchema(type));を呼び出すことによって、各タイプのいる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の静的コンストラクタの内部での契約を移入していますあなたは他の場所でそれをやりたいかもしれません。このスキーマは、V1スキーマと互換性があり、したがってV1メッセージはデータ損失なくV3クラスにデシリアライズすることができる

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

:など)V3.のスキーマが見えます。サンプルfiddle

もちろん、メンバーをMarketDataObjectからLowerStillBaseClassに移動する場合は、フィールドIDを同じにしておく必要があります。

この回避策の欠点は、タイプLowerStillBaseClassのオブジェクトを逆シリアル化する能力を失い、protobuf-netが正しい派生型を自動的に推測できるようにすることです。

+0

あなたには非常に詳細な回答と時間がありました。あなたが言っていることを完全に理解しています。 – screig

関連する問題