To serialized with protobuf, there is some necessary data about how to map the members - and note that each member itself must make sense to protobuf, or must be some kind of list.
If you can show the exact tree you are using I can help more, but for example:
[ProtoContract]
class Node<T>
{
[ProtoMember(1)]
public T Value { get; set; }
[ProtoMember(2, DataFormat= DataFormat.Group)]
public List<Node<T>> Children { get { return children; } }
private readonly List<Node<T>> children = new List<Node<T>>();
}
should serialize fine. The necessary data can also be supplied at runtime in "v2".
Based in part on an email conversation, I understand the model a bit better, and the important changes I see are:
- the value field must be annotated for serialization
- the class must be annotated
- there must be a parameterless constructor
- there must be something that it to use to add and enumerate the child lists
The last is an interesting one; I made the deliberate decision not to demand full IList
/IList<T>
there - all it needs is IEnumerable<T>
and an Add(T)
method, so I can add a private wrapper object that only exists for the purposes of serialization.
So, based on the email content:
using System;
using System.Collections.Generic;
using ProtoBuf;
static class Program
{
static void Main()
{
var tree = new NTree<int>(1);
tree.addChild(2);
var child = tree.addChild(3);
tree.addChild(4);
child.addChild(5);
child.addChild(6).addChild(7);
var clone = Serializer.DeepClone(tree);
DrawTree(tree);
Console.WriteLine();
Console.WriteLine();
DrawTree(clone);
}
static void DrawTree<T>(NTree<T> tree, int depth = 0)
{
var prefix = new string('\t', depth++);
Console.WriteLine(prefix + tree.Data);
foreach (var child in tree.Children) DrawTree(child, depth);
}
}
[ProtoContract]
class NTree<T>
{
[ProtoMember(1)]
T data;
LinkedList<NTree<T>> children;
internal T Data { get { return data; } } // added for demo only
internal IEnumerable<NTree<T>> Children { get { return children; } }// added for demo only
public NTree(T data)
{
this.data = data;
children = new LinkedList<NTree<T>>();
}
public NTree<T> addChild(T data) // changed just so I can build a tree for the demo
{
var child = new NTree<T>(data);
children.AddFirst(child);
return child;
}
public NTree<T> getChild(int i)
{
foreach (NTree<T> n in children)
if (--i == 0) return n;
return null;
}
private NTree()
{
children = new LinkedList<NTree<T>>();
}
[ProtoMember(2, DataFormat=DataFormat.Group)]
private NodeWrapper WrappedChildren {
get { return new NodeWrapper(children); }
}
private class NodeWrapper:IEnumerable<NTree<T>>
{ // only exists to help with serialization
private readonly LinkedList<NTree<T>> nodes;
public NodeWrapper(LinkedList<NTree<T>> nodes)
{
this.nodes = nodes;
}
public IEnumerator<NTree<T>> GetEnumerator()
{
return nodes.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return nodes.GetEnumerator();
}
public void Add(NTree<T> child) { nodes.AddLast(child); }
}
}
which should work for most T
you throw at it, except object
. Any unusual objects should probably themselves be data-contracts.
Notes for v2:
- you don't need the attributes; that can all be specified at runtime
- you don't need a parameterless constructor (although it is probably easiest to keep one, to make the
children
initiation easy)
- lists can be non-generic,
IEnumerable
and Add(object)
(but the expected type must be specified)