2
votes

I'm trying to serialize an immutable message that has an immutable base class. However, I'm unable to convince protobuf-serializer to deserialize it to a derived type. In short this test gives me an invalid cast exception (from Base to Derived):

[TestFixture]
public class InheritanceTest
{
    public class Base
    {
        public int IntField { get; private set; }

        public Base(int intField)
        {
            IntField = intField;
        }
    }

    public class Derived : Base
    {
        public string StringField { get; private set; }

        public Derived(int intField, string stringField) : base(intField)
        {
            StringField = stringField;
        }
    }

    [Test]
    public void TestInheritance()
    {
        var serializer = TypeModel.Create();
        serializer.Add(typeof (Base), true)
            .Add(1, "IntField")
            .AddSubType(2, typeof (Derived))
            .UseConstructor = false;

        serializer.Add(typeof (Derived), true)
            .Add(1, "StringField")
            .UseConstructor = false;

        serializer.CompileInPlace();

        using (var stream = new MemoryStream())
        {
            var message = new Derived(1, "Some text that is not important");

            serializer.Serialize(stream, message);
            stream.Position = 0;

            var retrieved = (Derived) serializer.Deserialize(stream, null, typeof (Derived));

            Assert.AreEqual(message.IntField, retrieved.IntField);
            Assert.AreEqual(message.StringField, retrieved.StringField);
        }
    }

}

This exception goes away if I convert Base and Derived to mutable types. Am I doing anything wrong or is it a limitation of protobuf-net?

2

2 Answers

1
votes
  [TestFixture]
public class InheritanceTest
{
    public class Base
    {
        public int IntField { get; private set; }

        public Base(int intField)
        {
            IntField = intField;
        }
    }

    public class Derived : Base
    {
        public string StringField { get; private set; }

        public Derived(int intField, string stringField) : base(intField)
        {
            StringField = stringField;
        }
    }

    [Test]
    public void TestInheritance()
    {
        var serializer = TypeModel.Create();
        serializer.Add(typeof (Base), true)
            .Add(1, "IntField")
            .UseConstructor = false;

        serializer.Add(typeof (Derived), true)
            .Add(1, "StringField")
            .UseConstructor = false;

        serializer.CompileInPlace();

        using (var stream = new MemoryStream())
        {
            var message = new Derived(1, "Some text that is not important");

            serializer.Serialize(stream, message);
            stream.Position = 0;

            var retrieved = (Derived) serializer.Deserialize(stream, null, typeof (Derived));

            Assert.AreEqual(message.IntField, retrieved.IntField);
            Assert.AreEqual(message.StringField, retrieved.StringField);
        }
    }

}

or

  [TestFixture]
    public class InheritanceTest
    {
        [DataContract]
        public class Base
        {
            [DataMember(Order = 1)]
            public int IntField { get; private set; }

            public Base(int intField)
            {
                IntField = intField;
            }
            protected Base(){}
        }

        [DataContract]
        public class Derived : Base
        {
            [DataMember(Order = 3)]
            public string StringField { get; private set; }

            public Derived(int intField, string stringField) : base(intField)
            {
                StringField = stringField;
            }
            private Derived(){}
        }

        [Test]
        public void TestInheritance()
        {
            RuntimeTypeModel.Default.Add(typeof(Base), true).AddSubType(2, typeof(Derived));

             using (var stream = new MemoryStream())
                {
                    var message = new Derived(1, "Some text that is not important");

                    Serializer.Serialize(stream, message);
                    stream.Position = 0;

                    var retrieved = Serializer.Deserialize<Derived>(stream);            

                    Assert.AreEqual(message.IntField, retrieved.IntField);
                    Assert.AreEqual(message.StringField, retrieved.StringField);
            }
        }

    }
0
votes

This looks to be a side-effect of the tuple-detection, causing it to have a specific code branch. The good nes is, you should be able to fix that from your original code, simply by changing true to false. This parameter tells is whether or not to apply standard behaviours. You don't need that since you are defining the model yourself.

        var serializer = TypeModel.Create();
        serializer.Add(typeof (Base), false)
            .Add(1, "IntField")
            .AddSubType(2, typeof (Derived))
            .UseConstructor = false;

        serializer.Add(typeof (Derived), false)
            .Add(1, "StringField")
            .UseConstructor = false;

Note I haven't verified this - I will check later though.