Juan
Juan

Reputation: 15715

Why do I get a "System.StackOverflowException was unhandled " exception when serializing?

I'm trying to serialize the following class:

[Serializable()]
public class BindingNode : IEnumerable<BindingNode>
{
    public BindingNode()
    {
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        throw new NotImplementedException();
    }

    public IEnumerator<BindingNode> GetEnumerator()
    {
        throw new NotImplementedException();
    }

    public void Add(BindingNode item)
    {
        throw new NotImplementedException();
    }
}

This was originally an ICollection instead of an IEnumerable, but I removed as much as I could of my code to keep only what caused the error. Here's the code where the exception occurs:

    private void button1_Click(object sender, EventArgs e)
    {
        BindingNode node = new BindingNode();
        if (saveFileDialog1.ShowDialog() == DialogResult.OK)
        {
            Stream stream = File.Open(saveFileDialog1.FileName, FileMode.Create);
            XmlSerializer xmlFormatter = new XmlSerializer(node.GetType());
            xmlFormatter.Serialize(stream, node);
            stream.Close();
        }            
    }

Upvotes: 0

Views: 11866

Answers (2)

Jon Hanna
Jon Hanna

Reputation: 113292

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.

Upvotes: 2

Jerod Houghtelling
Jerod Houghtelling

Reputation: 4867

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.

Upvotes: 2

Related Questions