2017-12-14 25 views
2

私たちは、protobuf-net v.2.3.2を使用して、プロジェクト内の複雑なオブジェクト(リスト、辞書などを含む)をシリアライズしてデシリアライズしています。たいていの場合、すべては問題ありませんが、まれに、非常に奇妙な動作が発生します。つまり、シリアライザの.FromProto<SomeComplexType>(bytes)メソッドの呼び出しが先行していない場合、あるプロセスで直列化されたオブジェクトは、 .ToProto(someComplexObject)に電話してください。ここで予期せぬprotobuf-netシリアライザの動作

は一例です:私たちのプロセス1は、次のようになりましょう:

class Program1 { 
    public static void Main() 
    { 
     SomeComplexType complexObject = new SomeComplexType(); 

     // Here goes some code filling complexObject with data 

     byte[] serialized = ToProto(complexObject); 

     File.WriteAllBytes("serialized.data", serialized); 
    } 

    public static byte[] ToProto(object value) 
    { 
     using (var stream = new MemoryStream()) 
     { 
      ProtoBuf.Serializer.Serialize(stream, value); 
      return stream.ToArray(); 
     } 
    } 

    public static T FromProto<T>(byte[] value) 
    { 
     using (var stream = new MemoryStream(value)) 
     { 
      return ProtoBuf.Serializer.Deserialize<T>(stream); 
     } 
    } 
} 

、我々はプロセス2に、そのオブジェクトを読み取ろうとしている。

class Program2 { 
    public static void Main() 
    { 
     byte[] serialized = File.ReadAllBytes("serialized.data"); 

     SomeComplexType complexObject =     
      FromProto<SomeComplexType>(serialized); 
    } 

    public static byte[] ToProto(object value) 
    { 
     using (var stream = new MemoryStream()) 
     { 
      ProtoBuf.Serializer.Serialize(stream, value); 
      return stream.ToArray(); 
     } 
    } 

    public static T FromProto<T>(byte[] value) 
    { 
     using (var stream = new MemoryStream(value)) 
     { 
      return ProtoBuf.Serializer.Deserialize<T>(stream); 
     } 
    } 
} 

我々が見る何をされますまれに、プロセス1がプロセス2をFromProtoの呼び出しで失敗させるファイルを生成することがあります(「パラメータのないコンストラクタが見つからない」から「StackOverflowException」までのさまざまなエラーが確認されました)。

ただし、FromProtoの呼び出しの前に、ToProto(new SomeComplexType());のような行を追加すると、エラーがなくなり、同じバイトセットが問題なくデシリアライズされます。他のメソッド(PrepareSerializer、GetSchemaを試してみました)はこのトリックをやっていないようです。

ToProtoとFromProtoがオブジェクトモデルを解析する方法には若干の違いがあるようです。もう1つのポイントは、ProtoBufがToProtoの呼び出し後に状態を「記憶」しているため、後続のデシリアライゼーションに役立つということです。

UPDATE:ここ は、より詳細である:

[ProtoContract(ImplicitFields = ImplicitFields.AllPublic)] 
[ProtoInclude(1, typeof(A))] 
[ProtoInclude(2, typeof(B))] 
public interface IBase 
{ 
    [ProtoIgnore] 
    string Id { get; } 
} 

[ProtoContract(ImplicitFields=ImplicitFields.AllPublic, AsReferenceDefault=true)] 
public class A : IBase 
{ 
    [ProtoIgnore] 
    public string Id { get; } 

    public string PropertyA { get; set; } 
} 

[ProtoContract(ImplicitFields=ImplicitFields.AllPublic, AsReferenceDefault=true)] 
public class B : IBase 
{ 
    [ProtoIgnore] 
    public string Id { get; } 

    public string PropertyB { get; set; } 
} 

[ProtoContract(ImplicitFields=ImplicitFields.AllPublic, AsReferenceDefault=true)] 
public class C 
{ 
    public List<IBase> ListOfBase = new List<IBase>(); 
} 

[ProtoContract(ImplicitFields=ImplicitFields.AllPublic, AsReferenceDefault=true)] 
public class D 
{ 
    public C PropertyC { get; set; } 
    public Dictionary<string, B> DictionaryOfBs { get; set; } 
} 

問題の根本的な原因はやや非のようです:私たちはこのようになります(非常に単純化された)き クラス構造Protobuf-netが型のシリアライザを準備する決定論的な方法。ここに私たちが観察していることがあります。

私たちには、プロデューサとコンシューマの2つのプログラムがあります。プロデューサはDのインスタンスを作成し、いくつかのデータを追加し、protobuf-netを使用してそのインスタンスをシリアライズします。消費者がそのシリアル化されたデータをピックアップし、プロデューサーでD.

のインスタンスにそれをデシリアライズ、いるProtobufは、時にはそれがIBASEを発見する前にBを入力発見ので、Bのためのシリアライザを生成し、B.

の直インスタンスとしてDictionaryOfBsの値をシリアル化

consumerでは、protobuf-netがIBaseを最初に検出する可能性があります。したがって、Bのシリアライザを準備すると、IBaseのサブクラスとして扱われます。したがって、DictionaryOfBsの値を逆シリアル化する場合、AとBを区別するためにフィールド番号を期待して、IBaseのサブクラスとしてそれらを読み取ろうとしています。ストリーム内のデータは、IBaseシリアライザが見るものがインスタンスAのBを(Mergeメソッドを使用して)変換しようとし、AをBをAに変換してBなどに変換しようとする無限再帰になり、最終的なStackOverflowExceptionが発生します。

シリアライザのシリアル化(ストリーム、新しいD())をデシリアライズする前に、シリアライザの作成順序が変更されるため、エラーは発生しませんが、幸運なことです。残念ながら、私たちの場合でさえ、それは時折"内部エラー、キーの不一致が発生しました"という理由で、満足できる回避策として使用できません。デシリアライズ時のエラー。

+0

xml、バイナリのような.netシリアル化を使用していない理由はありますか? –

+4

@Pranay BinaryFormatterを使わない理由はたくさんありますが、protobuf *はバイナリです(少なくともBinaryFormatterと同じ意味です)。理由としては、生成される出力のパフォーマンスとサイズです。 –

+0

@MarcGravell - ok、それは私にとって新しく、私は情報よりも読んでいます –

答えて

1

シリアライズコードは汎用APIを使用していますが、汎用型の推論により<object>が使用されています。これは物事を混乱させる可能性がある。最初に試してみるのは、ToProtoメソッドがSerializer.NonGeneric.Serializeを使用することです。これは.GetType()などを使用しますので、うまく混乱させないようにしてください。

または、ToProtoT valueと総称してください。

注:私はこれをテストしていませんが、試してみるのが最初のことです。

+0

Marcを更新しました。ありがとうございます。私はこのように、ジェネリックバージョンにToProtoを変更: パブリック静的バイト[] ToProto (T値) {Serializer.Serialize(ストリーム、値) {使用(=新規のMemoryStream()VARストリーム)。 return stream.ToArray(); } } もう一度テストを実行しました。残念ながら、同じ型のSerializeが最初に呼び出されない限り、私はまだStackOverflowExceptionを取得しています。 –

+0

@NikNik k;最近報告されたスタックオーバーフローも報告されたバグに関連する可能性があります。私はそれを試して修正するために遊ぶことができる完全にrunnable最小限のreproを持っている任意のチャンス? –

+0

申し訳ありませんが、そのデータとコードを共有することはできません。あなたが言及したバグへのリンクを投稿できますか? –

関連する問題