I\'m trying to serialize the following class:
[Serializable()]
public class BindingNode : IEnumerable
{
public BindingNode()
{
This would be your problem: BindingNode : IEnumerable<BindingNode>
This would be recursive and you would encouter a StackOverFlowException
. Typically people create two classes.
The single class:
public class BindingNode
{
/*..*/
}
The colleciton class:
public class BindingNodeCollection : IEnumerable<BindingNode>
{
/*..*/
}
This approach usually also increases cohesion and satisfies the Single Responsiblity Principle (SRP). It does this by seperating concerts. Collection logic is placed in the collection class and then the original class does what it was entended to do.
The default behaviour for XMLSerializer is hitting a loop because as part of trying to work out how to serialse a BindingNode
, it then tries to figure out how to serialise an IEnumerable<BindingNode>
, and to do that it tries to figure out how to serialise a BindingNode
.
There's nothing to say that you can't have BindingNode
implement IEnumerable<BindingNode>
, just that the default XMLSerializer behaviour isn't going to work.
If you implement IXmlSerializable, then you control the serialisation yourself. Since you already know the structure of a BindingNode, you don't have to work that out at runtime! If you've a guarantee of an acyclic graph (it's impossible to have a BindingNode that is an ancestor of itself), then this is trivial:
public void WriteXml(XmlWriter writer)
{
writer.WriteStartElement("BindingNode");
//More stuff here.
foreach(BindingNode contained in this)
contained.WriteXml(writer);
writer.WriteEndElement();
}
If the graph can by cyclic, it's just slightly more complicated in that you need to be able to instead of writing an element with all the details included, that you write an element that references a node already serialised to the stream, as otherwise the actual writing goes on forever and if you're lucky you hit a different cause of a stack overflow pretty soon (if you're unlucky the program merrily writes gigs and gigs of a file to disk first, and then hits it).
public int SomeSortOfUniqueID
{
get
{
//guess what this property has to do!
}
}
public void WriteXml(XmlWriter writer)
{
WriteXml(writer, new HashSet<BindingNode>());
}
private void WriteXml(XmlWriter writer, HashSet<BindingNode> alreadyWritten)
{
if(alreadyWritten.Add(this))
{
writer.WriteStartElement("BindingNode");
writer.WriteAttributeString("uniqueID", SomeSortOfUniqueID.ToString());
//More stuff here.
foreach(BindingNode contained in this)
contained.WriteXml(writer, alreadyWritten);
writer.WriteEndElement();
}
else
{
//we need to reference a node already mentioned in the document.
writer.WriteStartElement("BindingNode");
writer.WriteAttributeString("refID", SomeSortOfUniqueID.ToString());
writer.WriteEndElement();
}
}
Of course, you'll also have to implement ReadXml() to parse the XML back again.