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.
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
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]);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With