简体   繁体   中英

Protobuf-net and streams: InvalidCastException when not setting stream.Position = 0

Why do I have to set the serialized object's stream position manually to 0 if I want to deserialize it with protobuf-net?

I would expect that protobuf-net would always read my input stream from the beginning.

If I would like to have protobuf-net reading my stream from a different offset position (like reading a member of a class or a list by its tag), I would assume this would be an exceptional use case and I would have to handle it consciously different (api-wise).

(My version of protobuf-net was 640.)

Here's my test case which raises an exception, using inheritance:

using System.IO;
using ProtoBuf;

namespace ProtobufStreamTest
{
    public class Class1
    {
        static public void Main(string[] args)
        {
            var inheritingModel = new InheritingModel()
            {
                InheritingModelMember1 = "testinheriting",
                BaseModelMember1 = 42,
            };

            var ms = new MemoryStream();
            ProtoBuf.Serializer.Serialize(ms, inheritingModel);
            var originalStreamPos = ms.Position; // == 33
            // ms.Position = 0; // <== works okay, but why do I have to do this? Not setting position = 0 raises InvalidCastException: Unable to cast object of type 'ProtobufStreamTest.BaseModel' to type 'ProtobufStreamTest.InheritingModel'

            var deserialized = ProtoBuf.Serializer.Deserialize<InheritingModel>(ms);

            ms.Close();
        }
    }

    [ProtoContract]
    public class InheritingModel : BaseModel
    {
        [ProtoMember(4)]
        public string InheritingModelMember1 { get; set; }
    }

    [ProtoContract]
    [ProtoInclude(1, typeof(InheritingModel))]
    public class BaseModel
    {
        [ProtoMember(2)]
        public int BaseModelMember1 { get; set; }
    }
}

This test case raises no exception, no inheritance involved, but deserialized object has default (null) values:

using System.IO;
using ProtoBuf;

namespace ProtobufStreamTest
{
    public class Class1
    {
        static public void Main(string[] args)
        {
            var inheritingModel = new InheritingModel()
            {
                InheritingModelMember1 = "testinheriting",
            };

            var ms = new MemoryStream();
            ProtoBuf.Serializer.Serialize(ms, inheritingModel);
            var originalStreamPos = ms.Position; // == 16
            // ms.Position = 0; // works okay, but why do I have to do this? Not setting position to null results in a deserialized object but with null member values

            var deserialized = ProtoBuf.Serializer.Deserialize<InheritingModel>(ms);

            ms.Close();
        }
    }

    [ProtoContract]
    public class InheritingModel
    {
        [ProtoMember(1)]
        public string InheritingModelMember1 { get; set; }
    }
}

Why do I have to set the serialized object's stream position manually to 0 if I want to deserialize it with protobuf-net?

That is normal practice for any API that accepts a Stream ; indeed, many (most?) streams are non-seekable. It would be highly unusual to automatically try to reset the position.

I would expect that protobuf-net would always read my input stream from the beginning. If I would like to have protobuf-net reading my stream from a different offset position (like reading a member of a class or a list by its tag), I would assume this would be an exceptional use case and I would have to handle it consciously different (api-wise).

I would very much not expect that. That simply isn't how Stream s are used - unrelated to protobuf-net, but more: in the general case. Try it with just about any API (serialization, compression, encryption, data-transfer, etc): you will find that almost all will work this same way, and I would argue that those that don't (but which instead: reset the position) are incorrect implementations .

There are two fundamental parts to why :

  • because many streams are not seekable, the consumer should never assume that it can reset the position to zero; nor should it have 2 radically different behaviours for seekable vs non-seekable - thus the only well behaved implementation is: don't assume you can seek
  • in many cases, a stream is being consumed in multiple parts - ie it might be 300 bytes of X, then 200 bytes of Y, then 12 bytes of Z; when reading Y and Z, it would be disastrous and unexpected (ie virtually no APIs would ever do this) if the stream kept resetting

Basically; your expectation is not the normal case.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM