2016-11-11 16 views
1

protobuf-net.2.1.0いるProtobufネット:シンプル継承:デシリアライズ、タイプとしてシリアライズサブタイプは、InvalidCastExceptionが

を投げるよう私の理解では、protobuf-netが完全に受信機側で利用可能な情報に基づいてデシリアライズのメッセージコントラクトを決めるということです - シリアライズされたパケット自体はメッセージ契約を構築するために依存しません。具体的には、クラスメンバ属性は、データ型と、パケット内に見つかると予想されるフィールドの順序付けを示します。

したがって、送信側は受信側とは独立しているため、フィールドデータ&の順序が受信側プロトタイプ契約で定義された順序と一致する場合は、シリアル化されたパケットを特定のタイプとして解釈できる必要があります。

特に、継承に関しては、継承が正しく表記されている場合は、基本型のオブジェクトを直列化してサブタイプのオブジェクトとして直列化することが可能でなければなりません。

しかし、単純な継承階層DerivedClass : BaseClassのために、私は私がBaseClassとしてシリアライズしDerivedClassとしてデシリアライズ場合、返されるオブジェクトの型BaseClassのものであろうことがわかります。ここで

はクラスです:

public class TestClass 
{ 
    public static void Test() 
    { 
     var baseObject = new BaseClass { Name = "BaseObject" }; 
     var derivedObject = new DerivedClass { Name = "DerivedObject", Index = 1 }; 

     using (var stream = new MemoryStream()) 
     { 
      ProtoBuf.Serializer.Serialize(stream, baseObject); 
      Debug.WriteLine(stream.Length); 
      stream.Seek(0, SeekOrigin.Begin); 

      // either of next two lines will throw the invalid cast exception : 
      // DerivedClass derivedObjectOut = ProtoBuf.Serializer.Deserialize<DerivedClass>(stream); 
      // var objectOut = ProtoBuf.Serializer.Deserialize<DerivedClass>(stream); 

      // no exception thrown but internal type of objectOut is unexpectedly BaseClass : 
      var objectOut = ProtoBuf.Serializer.Deserialize<DerivedClass>(stream); 
     } 
    } 
} 

が例外を生成します:

[ProtoBuf.ProtoInclude(1000, typeof(DerivedClass))] 
[ProtoBuf.ProtoContract] 
public class BaseClass 
{ 
    [ProtoBuf.ProtoMember(1, IsRequired = false, Name = @"Name", DataFormat = ProtoBuf.DataFormat.TwosComplement)] 
    public string Name { get; set; } 
} 

[ProtoBuf.ProtoContract] 
public class DerivedClass : BaseClass 
{ 
    [ProtoBuf.ProtoMember(2, IsRequired = false, Name = @"Index", DataFormat = ProtoBuf.DataFormat.TwosComplement)] 
    public int Index { get; set; } 
} 

は、以下の試験方法の実行

タイプの例外を 'System.InvalidCastExceptionの' で発生しました protobuf-net.dllが、ユーザコード

で処理されませんでした

追加情報:タイプ 'protobuf_net.lib.ProtoClasses.SimpleBaseClass'のオブジェクトをキャストして 'protobuf_net.lib.ProtoClasses.SimpleDerivedClass'と入力できません。

+0

@dbcおかげで(私の投稿を編集してコードが説明と一致するようにしました – BaltoStar

答えて

0

ここにprotobuf-netには制限やバグがあるようです。 TypeModel.DeserializeCore()の方法は、ベースコントラクトタイプを見つけ、そのタイプとしてデシリアライズし始め、派生タイプのタグを検出すると、そのタイプをデシリアライズするように切り替わります。最後に、のオブジェクトは、タイプが構築され、生成され、目的の派生型のタグは決して観察されなかったので、問題が発生します。 DerivedTypeの事前に割り当てられたインスタンスにストリームをマージするSerializer.Merge<T>()を使用します:

var baseObject = new BaseClass { Name = "BaseObject" }; 

using (var stream = new MemoryStream()) 
{ 
    ProtoBuf.Serializer.Serialize(stream, baseObject); 
    Debug.WriteLine(stream.Length); 
    stream.Seek(0, SeekOrigin.Begin); 
    var derivedObjectOut = ProtoBuf.Serializer.Merge(stream, new DerivedClass()); 
} 

それはコードのにおいのビットを持っていますが、問題を修正

幸い、簡単な回避策があります。

ちなみに、元のサンプルコードでは、のストリームを2回読み込もうとすると、巻き戻しは行われません。。 2番目の呼び出しでタグが見つからないため、同様の例外がスローされます。

更新

あなたは、一般的なデシリアライズコードを記述する場合は、継承階層のProtoIncludeAttributeどこかの存在をテストすることができ、そして存在する場合Merge()を呼び出し、以下のヘルパーメソッドを使用して:

public static class ProtobufExtensions 
{ 
    public static T DeserializeOrMerge<T>(Stream stream) 
    { 
     if (!typeof(T).IsValueType 
      && typeof(T) != typeof(string) 
      // Test to make sure T has a public default constructor 
      && typeof(T).GetConstructor(Type.EmptyTypes) != null 
      && typeof(T).HasProtoIncludeAtributes()) 
     { 
      return ProtoBuf.Serializer.Merge(stream, Activator.CreateInstance<T>()); 
     } 
     else 
     { 
      return ProtoBuf.Serializer.Deserialize<T>(stream); 
     } 
    } 

    public static bool HasProtoIncludeAtributes(this Type type) 
    { 
     if (type == null) 
      throw new ArgumentNullException(); 
     if (!type.IsDefined(typeof(ProtoContractAttribute))) 
      return false; 
     return type.BaseTypesAndSelf().SelectMany(t => t.GetCustomAttributes<ProtoIncludeAttribute>()).Any(); 
    } 

    public static IEnumerable<Type> BaseTypesAndSelf(this Type type) 
    { 
     while (type != null) 
     { 
      yield return type; 
      type = type.BaseType; 
     } 
    } 
} 

はそれが好きで使用します。これらのタイプミスをキャッチするため

var baseObject = new BaseClass { NameInBaseClass = "BaseObject" }; 

using (var stream = new MemoryStream()) 
{ 
    ProtoBuf.Serializer.Serialize(stream, baseObject); 
    Debug.WriteLine(stream.Length); 
    stream.Seek(0, SeekOrigin.Begin); 

    var derivedObjectOut = ProtobufExtensions.DeserializeOrMerge<DerivedClass>(stream); 
} 
+0

protobuf-netの内部はとても面白いです。残念ながら、私はシリアライゼーション/デシリアライズに関連するすべてのコードを生成しているので、 'Deserialize'フォームではなく' Merge'フォームを呼び出すコードを生成するのは難しいでしょう。 'マージ'フォーム? – BaltoStar

+0

@BaltoStar - 回答が更新されました。 – dbc

関連する問題