5

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.

Simon Moles
  • 359
  • 1
  • 5
  • 11

2 Answers2

4

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

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • Excellent answer! Nice to hear it's going to be in a future release. The workaround works fine for now. (I added it to the static constructor for ResourceNode<>). Am I right in thinking that DynamicType etc are diverging quite far from the protobuf standard? – Simon Moles Feb 23 '12 at 15:39
  • @Simie - watch out - the static ctor is per-T, and I is quite tricky to guess when it will fire. Mainly due to the tricky "when" I would avoid the .cctor for this. – Marc Gravell Feb 23 '12 at 18:26
  • Yep, just found that out. I've moved it somewhere else. Thanks. – Simon Moles Feb 23 '12 at 19:03
1

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]);
    }
}