私たちは、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())をデシリアライズする前に、シリアライザの作成順序が変更されるため、エラーは発生しませんが、幸運なことです。残念ながら、私たちの場合でさえ、それは時折"内部エラー、キーの不一致が発生しました"という理由で、満足できる回避策として使用できません。デシリアライズ時のエラー。
xml、バイナリのような.netシリアル化を使用していない理由はありますか? –
@Pranay BinaryFormatterを使わない理由はたくさんありますが、protobuf *はバイナリです(少なくともBinaryFormatterと同じ意味です)。理由としては、生成される出力のパフォーマンスとサイズです。 –
@MarcGravell - ok、それは私にとって新しく、私は情報よりも読んでいます –