【问题标题】:protobuf-net : simple inheritance : serialize as type , deserialize as sub-type throws InvalidCastExceptionprotobuf-net:简单继承:序列化为类型,反序列化为子类型抛出 InvalidCastException
【发布时间】:2016-11-11 23:15:16
【问题描述】:

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.dll 但未在用户代码中处理

附加信息:无法转换类型的对象 'protobuf_net.lib.ProtoClasses.SimpleBaseClass' 键入 'protobuf_net.lib.ProtoClasses.SimpleDerivedClass'。

【问题讨论】:

  • @dbc 感谢您发现这些错别字(从错误的来源复制/粘贴)——我已经编辑了我的帖子,因此代码与描述相符

标签: c# inheritance serialization deserialization protobuf-net


【解决方案1】:

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

幸运的是,有一个简单的解决方法:使用Serializer.Merge&lt;T&gt;() 将流合并到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);
}

【讨论】:

  • 感谢@dbc,protobuf-net 的内部结构非常有趣。不幸的是,由于我正在生成与序列化/反序列化相关的所有代码,因此生成调用Merge 表单而不是Deserialize 表单的代码将会很棘手——或者你是说可以始终使用Merge 表格?
猜你喜欢
  • 1970-01-01
  • 2012-12-01
  • 2012-02-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多