Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Protobuff.net can't serialise Interface

I'm getting the error

The type cannot be changed once a serializer has been generated

When attempting to serialise with Protobuff.net. I've managed to reduce the code to find the culprit, but would like to know why it's not able to serialise this property.

I've found a working solution that I'm able to use, but am interested in the explanation of why this code fails.

Wont serialise:

[ProtoContract]
public class SomeController
{
    [ProtoMember(3)]
    public int ControllerValue { get; set; }

    [ProtoMember(4, AsReference = true)]
    private ITest ITestObj { get; set; }

    private SomeController(){}
    public SomeController(object something, int value)
    {
        ControllerValue = value;
        ITestObj = something as ITest;
    }
}

Will serialise:

The error is caused by SomeController.ITestObj. If I change this class to:

[ProtoContract]
public class SomeController
{
    [ProtoMember(3)]
    public int ControllerValue { get; set; }

    [ProtoMember(4, AsReference = true)]
    private TestObj OriginalObject { get; set; }

    private ITest ITestObj => OriginalObject as ITest;

    private SomeController(){}
    public SomeController(TestObj something, int value)
    {
        ControllerValue = value;
        OriginalObject = something;
    }
}

It works fine.

Working code:

Below is a self contained HTTP handler which will run this code and reproduce the error:

using System.IO;
using System.Web;
using ProtoBuf;

namespace Handlers
{
    /// <summary>
    /// Summary description for Test
    /// </summary>
    public class Test : IHttpHandler
    {
        [ProtoContract]
        public class TestObj : ITest
        {
            [ProtoMember(1, AsReference = true)]
            public SomeController SomeController { get; set; }

            [ProtoMember(2)]
            public int SomeValue { get; set; }

            private TestObj(){}
            public TestObj(int something)
            {
                SomeController = new SomeController(this, something + 1);
                SomeValue = something;
            }
        }

        [ProtoContract]
        public class SomeController
        {
            [ProtoMember(3)]
            public int ControllerValue { get; set; }

            [ProtoMember(4, AsReference = true)]
            private ITest ITestObj { get; set; }

            private SomeController() { }
            public SomeController(object something, int value)
            {
                ControllerValue = value;
                ITestObj = something as ITest;
            }
        }

        [ProtoContract]
        [ProtoInclude(5, typeof(TestObj))]
        public interface ITest
        {
            [ProtoMember(6, AsReference = true)]
            SomeController SomeController { get; set; }

            [ProtoMember(7)]
            int SomeValue { get; set; }
        }


        public void ProcessRequest(HttpContext context)
        {
            var testObj = new TestObj(5);
            var serialised = Serialiser.Serialise(testObj);
            var deserialised = Serialiser.Deserialise<TestObj>(serialised);
            HttpContext.Current.Response.Write(deserialised.SomeValue + "|" + deserialised.SomeController.ControllerValue + "<br>");
        }

        protected internal class Serialiser
        {
            protected internal static byte[] Serialise<T>(T objectToSerialise)
            {
                using (var stream = new MemoryStream())
                {
                    Serializer.Serialize(stream, objectToSerialise);
                    return stream.ToArray();
                }
            }

            protected internal static T Deserialise<T>(byte[] bytes)
            {
                using (var stream = new MemoryStream(bytes))
                {
                    return Serializer.Deserialize<T>(stream);
                }
            }
        }

        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }
}
like image 643
Tom Gullen Avatar asked Sep 07 '25 18:09

Tom Gullen


1 Answers

Interfaces are ... awkward. The good news is you can give it an extra hint in the code (before you start serializing):

Serializer.PrepareSerializer<ITest>();

It would be nice if the code could detect this better in advance, but: right now the above should help. So as an example, I've put this code in the static type initializer:

static Handler1()
{
    Serializer.PrepareSerializer<ITest>();
}

But it could also go in global.asax or anywhere else that happens before you start serializing.

like image 176
Marc Gravell Avatar answered Sep 10 '25 15:09

Marc Gravell