2016-11-11 33 views
1

protobuf-net.2.1.0protobuf網:簡單的繼承:序列類型,序列化作爲分型引發InvalidCastException

我的理解是,protobuf-net確定反序列化完全基於信息基礎上的接收方提供的消息,合同 - 序列化的數據包本身並不依賴於構建消息協議。具體而言,類成員屬性指示數據類型和預期在數據包中找到的字段的順序。因此,由於發送方獨立於接收方,如果字段數據&命令匹配由接收方協議定義,它應該有可能將任何序列化數據包解釋爲特定類型。

具體而言,關於繼承,應該可以序列化一個base-type對象和de-serialize作爲子類型的對象 - 前提是繼承是正確標記的。

然而,對於一個簡單的繼承層次DerivedClass : BaseClass,我發現,如果我作爲序列化和BaseClass反序列化爲DerivedClass,返回的對象將是BaseClass類型。

下面是類:

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

執行以下測試方法:

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-net.dll但未在用戶代碼中處理

其他信息:無法投射 'protobuf_net.lib.ProtoClasses.SimpleBaseClass'類型的對象以鍵入 'protobuf_net.lib.ProtoClasses.SimpleDerivedClass'。

+0

捕捉那些錯別字@dbc感謝(從錯誤的來源複製/粘貼) - 我修改了我的帖子,以便代碼與描述 – BaltoStar

回答

0

這裏似乎有一個protobuf-net的限制或bug。 TypeModel.DeserializeCore()的工作方式是它找到合同類型基地,開始反序列化爲該類型,並且當它遇到派生類型的標記時,切換到反序列化該類型。最後,觀察的一個對象類型被構造並填充,導致您看到的問題,因爲您從未觀察到所需派生類型的標記。

幸運的是,有一個簡單的解決方法:使用Serializer.Merge<T>()到流合併成的DerivedType預分配的實例:

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

它有一個位代碼的氣味,但解決問題。

順便說一句,在您的原始示例代碼中,您嘗試讀取兩次流而不倒回。這也會引發類似的異常,因爲沒有標籤遇到第二個調用。

更新

如果你正在編寫通用的反序列化的代碼,你可以測試在繼承層次結構中的ProtoIncludeAttribute某處存在,如果存在的話稱Merge(),使用下面的helper方法:

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

相匹配,感謝@dbc protobuf-net的內部內容非常有趣。不幸的是,由於我生成了所有與序列化/反序列化相關的代碼,因此生成調用「合併」表單而不是「反序列化」表單的代碼會非常棘手 - 或者您說可以始終使用合併表格? – BaltoStar

+0

@BaltoStar - 答案已更新。 – dbc

相關問題