Search code examples
c#protobuf-net

protobuf - serialization using protobuf-net with inheritance


Getting "Unexpected sub type" when serializing.

var model = RuntimeTypeModel.Create();
var baseType = model.Add(typeof(BaseClass), true, CompatibilityLevel.Level300);
var child = baseType.AddSubType(10, typeof(ChildOfBase));
child.AddSubType(20, typeof(GrandChildToBase));
using (var stream = new MemoryStream())
{                                  
    model.Serialize(stream, grandChildInstance); //ERROR : Unexpected sub type GrandChildToBase
}

Getting "Unexpected sub type GrandChildToBase" Note: Although, I set CompatLevel to 300, it's defaulting to 200. I am on version 3.0.101

I don't want to use "ProtoInclude" on the BaseClass. (All my classes are distributed independently via nuget packages and having knowledge of child classes in base class breaks that)

EDIT 1 Serialization is working now! (Thanks Marc Gravell) But, deserialization is not (Getting "Unable to cast object of type BaseClass to GrandChildToBase")..

var model = RuntimeTypeModel.Create();
model.DefaultCompatibilityLevel = CompatibilityLevel.Level300;
var baseType = model.Add(typeof(BaseClass));
var childType = model.Add(typeof(ChildOfBase));
model.Add(typeof(GrandChildToBase)); // don't need to capture result of this one

baseType.AddSubType(10, typeof(ChildOfBase));
childType.AddSubType(20, typeof(GrandChildToBase));
byte[] result = null;
using (var stream = new MemoryStream())
{
    GrandChildToBase grandChildInstance = new();
    model.Serialize(stream, grandChildInstance);
    result = stream.ToArray();
}
using (var stream = new MemoryStream(result))
    {                   
        var payloadObj = Serializer.Deserialize<GrandChildToBase>(stream); ***//ERROR: "Unable to cast object of type BaseClass to GrandChildToBase"***                  
    }

EDIT 2 Per @Marc Gravell's clarification, I need to use the custom "model" and not the "default" serializer.


Solution

  • The error here is that the AddSubType API is intended for chained builder usage, and effectively returns the same object you passed in - or specifically: the result of AddSubType is not the MetaType representing the sub-type; you can see this via:

    var child = baseType.AddSubType(10, typeof(ChildOfBase));
    Console.WriteLine(ReferenceEquals(child, baseType)); // outputs True
    

    This means that when you then do

    child.AddSubType(20, typeof(GrandChildToBase));
    

    you're effectively adding GrandChildToBase to BaseClass, not to ChildOfBase.

    I will acknowledge that the library should have told you about this clearly in the .AddSubType(20, typeof(GrandChildToBase)) step! I'll check to see whether we can add an assertion there.

    However! To fix the code:

    var model = RuntimeTypeModel.Create();
    model.DefaultCompatibilityLevel = CompatibilityLevel.Level300;
    var baseType = model.Add(typeof(BaseClass));
    var childType = model.Add(typeof(ChildOfBase));
    model.Add(typeof(GrandChildToBase)); // don't need to capture result of this one
    
    baseType.AddSubType(10, typeof(ChildOfBase));
    childType.AddSubType(20, typeof(GrandChildToBase));
    using (var stream = new MemoryStream())
    {
        GrandChildToBase grandChildInstance = new();
        model.Serialize(stream, grandChildInstance); //ERROR : Unexpected sub type GrandChildToBase
    }
    

    As a side note: the 10 vs 20 - these are not in conflict with each-other, as they are applied (now) at different levels in the hierarchy; they could both be 10 or any other number if you like.