protobuf-net:简单继承:序列化为类型,反序列化为子类型抛出InvalidCastException

时间:2016-11-11 23:15:17

标签: c# inheritance serialization deserialization protobuf-net

protobuf-net.2.1.0

我的理解是protobuf-net完全基于接收方可用的信息来确定反序列化的消息契约 - 序列化的数据包本身不依赖于构造消息契约。具体来说,类成员属性指示数据类型和预期在数据包中找到的字段的顺序。

因此,由于发送方侧独立于接收方,因此如果字段数据和字段数据,则应该可以解释某种类型的任何序列化分组。订单匹配由接收方原型合同定义。

特别是关于继承,应该可以序列化基类型的对象并反序列化为子类型的对象 - 只要正确地标记了继承。

但是,对于简单的继承层次结构DerivedClass : BaseClass,我发现如果我序列化为BaseClass并反序列化为DerivedClass,则返回的对象将为{{1}类型}。

以下是课程:

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

产生例外:

  

类型' System.InvalidCastException'的例外情况发生在   protobuf-net.dll但未在用户代码中处理

     

附加信息:无法投射类型的对象   ' protobuf_net.lib.ProtoClasses.SimpleBaseClass'输入   ' protobuf_net.lib.ProtoClasses.SimpleDerivedClass'

1 个答案:

答案 0 :(得分:0)

这里似乎存在protobuf-net的限制或错误。 TypeModel.DeserializeCore()的工作方式是找到 base 契约类型,开始反序列化为该类型,当遇到派生类型的标记时,切换到反序列化该类型。最后,构建并填充观察类型的对象,从而导致您看到的问题,因为从未观察到所需派生类型的标记。

幸运的是,有一个简单的解决方法:使用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());
}

它有一点代码味道,但修复了问题。

顺便说一句,在原始示例代码中,您尝试两次读取流而不重写。这也会引发类似的异常,因为第二次调用会遇到 no 标记。

<强>更新

如果您正在编写通用反序列化代码,则可以在继承层次结构中的某处测试是否存在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);
}