1
votes

Background

I'm trying to get the below class to serialize and deserialize a class that implements an abstract class using Protobuf-net. However, it fails when deserializing with the error: "No parameterless constructor found for AbstractTest".

What am I doing incorrectly?

What I Have Tried

I have spent much time researching for an answer, but very few questions involve the abstract nature of my question.

Minimal Example

    public class TestRunner
    {
        public void Go()
        {
            string encoded = Serialize<ConcreteTest>(new ConcreteTest());

            object ob = Deserialize(new System.IO.MemoryStream(Encoding.ASCII.GetBytes(encoded)));
        }

        public static string Serialize<T>(T data)
        {
            MemoryStream outputStream = new MemoryStream();
            Serializer.Serialize<T>(outputStream, data);
            outputStream.Position = 0;

            StreamReader outputReader = new StreamReader(outputStream);
            return outputReader.ReadToEnd();
        }

        public static object Deserialize(Stream data)
        {
             AbstractTest test = Serializer.Deserialize<AbstractTest>(data);
             //Error from the line above: No parameterless constructor found for AbstractTest

            if (test.ID == 3)
            {
                 return Serializer.Deserialize<ConcreteTest>(data);
            }

            throw new Exception("We don't know how to handle the message type if I got here!");
        }
    }


[ProtoBuf.ProtoContract]
[ProtoBuf.ProtoInclude(25, typeof(ConcreteTest))]
public abstract class AbstractTest
{
    public AbstractTest()
    {

    }

    [ProtoBuf.ProtoMember(1)]
    public int ID = 3;

    public abstract void Test();
}


[ProtoBuf.ProtoContract]
public class ConcreteTest : AbstractTest
{
    [ProtoBuf.ProtoMember(2)]
    public int ID2 = 4;
    public override void Test()
    {
         MasterLog.DebugWriteLine("It worked!");
    }
}
1
FYI my answer here presumes that you do genuinely need the data as a text string. However, note that usually it is better to simply avoid needing the data as a string, so if possible I'd advise simply not doing that. If you aren't sure and want to discuss what you're doing to figure out whether a string can be avoided, let me know. - Marc Gravell

1 Answers

0
votes

The initial problem is that you corrupted the data by using an Encoding backwards. An Encoding converts arbitrary text to formatted binary (meaning: formatted according to the rules of that "encoding"), and formatted binary back to arbitrary text. It is designed to describe how to write text onto a stream. You have used it backwards, meaning: you have used it convert arbitrary binary in a different format into a string. You might also have actually used two different encodings (StreamReader almost certainly won't use ASCII by default).

So: you can't use an Encoding here. What you can use is base-64; for example:

using (var outputStream = new MemoryStream())
{
    Serializer.Serialize<T>(outputStream, data);

    return Convert.ToBase64String(outputStream.GetBuffer(),
        0, (int)outputStream.Length);
}

You can reverse this using Convert.FromBase64String:

using (var ms = new System.IO.MemoryStream(Convert.FromBase64String(encoded)))
{
    object ob = Deserialize(ms);
    Console.WriteLine(ob.ToString());
}

There's also a problem where you deserialize a stream twice without rewinding, which means that the second deserialize works on zero bytes, which means that it will try to instantiate an abstract type; so you also need to fix the Deserialize method:

public static object Deserialize(Stream data)
{
    AbstractTest test = Serializer.Deserialize<AbstractTest>(data);

    return test;
}

With this done, it all works correctly.