Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ProtoBuf-Net ProtoInclude Generic Type Subclass

Tags:

protobuf-net

I'm having some problems with ProtoBuf-Net with a subclass of an object which inherits from a generic class.

My inheritance tree looks like this:

Node
    SomeNodeType
    SomeOtherType
    ResourceNode<T>
        ShipResource : ResourceNode<Ship>
        SomeResource : ResourceNode<SomeType>

I've been using ProtoInclude on the base Node type for all the normal types.

What would be the best way of achieving this hierarchy with protobuf-net? I've tried just including everything, but I get errors which seem to stem from protobuf trying to deserialise the object as one of it's parent objects.

like image 257
Simon Moles Avatar asked Feb 23 '12 06:02

Simon Moles


2 Answers

You're probably seeing:

A type can only participate in one inheritance hierarchy

at the moment, right?

The issue becomes clearer when you recall that ResourceNode<T> is not a closed type - but ResourceNode<Ship> and ResourceNode<SomeType> are. This means 2 things:

Firstly, Node needs to know separately about the two (ResourceNode<Ship> and ResourceNode<SomeType>), and secondly: we need to tell ResourceNode<Ship> about ShipResource only, and ResourceNode<SomeType> about SomeResource only.

The first is easy enough with the attribute approach:

[ProtoContract]
[ProtoInclude(1, typeof(SomeNodeType)), ProtoInclude(2, typeof(SomeOtherType))]
[ProtoInclude(3, typeof(ResourceNode<Ship>))]
[ProtoInclude(4, typeof(ResourceNode<SomeType>))]
public class Node { }

However, the second bit can't be cleanly expressed in any current release. We can't currently use:

[ProtoContract]
[ProtoInclude(1, typeof(ShipResource)), ProtoInclude(1, typeof(SomeResource))]
public class ResourceNode<T> : Node { }

since those attributes apply to both of ResourceNode<Ship> and ResourceNode<SomeType>, and represent illegal inheritance chains. The duplicated 1 in the above is intentional, as they are not in conflict, again because they are parallel branches.

What we can do, in v2, is configure this relationship explicitly:

RuntimeTypeModel.Default.Add(typeof(ResourceNode<Ship>), true)
     .AddSubType(1, typeof (ShipResource));
RuntimeTypeModel.Default.Add(typeof(ResourceNode<SomeType>), true)
     .AddSubType(1, typeof(SomeResource));

What I want to do is tweak the resolver such that it is able to detect this as a common-case, so that you can simply use the attributes:

[ProtoContract]
[ProtoInclude(1, typeof(ShipResource)), ProtoInclude(1, typeof(SomeResource))]
public class ResourceNode<T> : Node { }

I have added a "todo" item and failing test for this. However, interestingly while setting that up I also found a scenario where something isn't playing happily, so I'll need to fix that first

like image 123
Marc Gravell Avatar answered Oct 24 '22 17:10

Marc Gravell


I had the exact same problem, but rather than configuring all the types by hand, the method below seems to work for any type. Call it prior to serialization/deserialization.

private void PopulateTypes(Type t)
{
    foreach(object mt in RuntimeTypeModel.Default.GetTypes())
    {
        MetaType theType = mt as MetaType;
        if (null != theType)
        {
            if (theType.Type == t)
                return;
        }
    }

    Type objType = typeof (object);
    List<Type> inheritanceTree = new List<Type>();
    do
    {
        inheritanceTree.Insert(0, t);
        t = t.BaseType;
    } while (null != t && t != objType);

    if (!inheritanceTree.Any(gt => gt.IsGenericType))
        return;

    int n = 100;
    for (int i = 0; i < inheritanceTree.Count - 1; i++)
    {
        Type type = inheritanceTree[i];
        MetaType mt = RuntimeTypeModel.Default.Add(type, true);
        mt.AddSubType(n++, inheritanceTree[i + 1]);
    }
}
like image 35
Menno Deij - van Rijswijk Avatar answered Oct 24 '22 19:10

Menno Deij - van Rijswijk