简单继承:序列化为类型,反序列化为子类型抛出InvalidCastException
本文关键字:类型 InvalidCastException 反序列化 序列化 继承 简单 | 更新日期: 2023-09-27 18:17:27
protobuf-net.2.1.0
我的理解是,protobuf-net
完全基于接收端可用的信息来确定反序列化的消息契约——不依赖于序列化的数据包本身来构建消息契约。具体地说,类成员属性指明了期望在包中找到的字段的数据类型和顺序。
因此,由于发送端独立于接收端,因此应该可以将任何序列化的数据包解释为特定类型的字段数据&订单匹配接收方合同原型。
特别是在继承方面,应该可以序列化基本类型的对象,并将其反序列化为子类型的对象——前提是继承被正确地标记。
然而,对于一个简单的继承层次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);
}
}
}
产生异常:
类型为'System '的异常。InvalidCastException'发生在但在用户代码中未处理
附加信息:无法强制转换类型的对象"protobuf_net.lib.ProtoClasses。SimpleBaseClass'的类型"protobuf_net.lib.ProtoClasses.SimpleDerivedClass"。
似乎在protobuf-net中有一个限制或错误。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()
:
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);
}