【问题标题】:Why do I get a "System.StackOverflowException was unhandled " exception when serializing?为什么我在序列化时会收到“System.StackOverflowException was unhandled”异常?
【发布时间】:2010-08-10 21:12:36
【问题描述】:

我正在尝试序列化以下类:

[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();
    }
}

这最初是一个 ICollection 而不是 IEnumerable,但我尽可能多地删除了我的代码,只保留导致错误的原因。以下是发生异常的代码:

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

【问题讨论】:

  • 附带说明,您不需要为 XmlSerializer 标记您的类 Serializable。
  • 另外,您应该使用“使用”块来清理流,而不仅仅是 .Close。如您所见,序列化代码可能会抛出这可能会为文件留下一个打开的句柄。

标签: c# .net


【解决方案1】:

XMLSerializer 的默认行为是循环,因为作为尝试解决如何序列化 BindingNode 的一部分,它会尝试找出如何序列化 IEnumerable&lt;BindingNode&gt;,并为此尝试弄清楚了解如何序列化BindingNode

没有什么可以说你不能让BindingNode 实现IEnumerable&lt;BindingNode&gt;,只是默认的 XMLSerializer 行为不起作用。

如果您实现了 IXmlSerializable,那么您可以自己控制序列化。由于您已经知道 BindingNode 的结构,因此您不必在运行时解决它!如果你有一个无环图的保证(不可能有一个 BindingNode 是它自己的祖先),那么这很简单:

public void WriteXml(XmlWriter writer)
{
    writer.WriteStartElement("BindingNode");
    //More stuff here.
    foreach(BindingNode contained in this)
        contained.WriteXml(writer);
    writer.WriteEndElement();
}

如果图形可以是循环的,那么它会稍微复杂一点,因为您需要能够编写一个引用已序列化到流的节点的元素,而不是编写包含所有细节的元素,如否则实际的写入将永远持续下去,如果幸运的话,您很快就会遇到堆栈溢出的不同原因(如果您不走运,程序会愉快地将文件的 gigs 和 gigs 先写入磁盘,然后点击它)。

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

当然,您还必须实现 ReadXml() 以再次解析 XML。

【讨论】:

  • 哦,即使图是非循环的,如果节点可以有多个父节点,第二个选项可能会更好,因为同一个节点仍然有可能被序列化两次。虽然它不会导致循环,但会造成浪费,并且会反序列化为两个不同的对象,这不仅会进一步浪费,还会失去身份的属性。
【解决方案2】:

这将是您的问题:BindingNode : IEnumerable&lt;BindingNode&gt; 这将是递归的,您将遇到StackOverFlowException。通常人们会创建两个类。

单一类:

public class BindingNode
{
   /*..*/
}

收集类:

public class BindingNodeCollection : IEnumerable<BindingNode>
{
   /*..*/
}

这种方法通常还可以增加凝聚力并满足单一职责原则 (SRP)。它通过分开音乐会来做到这一点。集合逻辑被放置在集合类中,然后原始类执行它打算做的事情。

【讨论】:

  • 所以我想除了实现IEnumerable的非通用版本之外没有其他解决方案了吗?
  • 唯一想到的其他解决方案是创建两个类。一种是BindingNode,另一种是BindingNodeCollection。后者可以继承自IEnumerable&lt;BindingNode&gt;
  • 好吧,我一直在这样做,直到我发现这会导致另一个问题:stackoverflow.com/questions/3452376/…
  • 我想我明白你现在想要完成什么。我已就您的其他问题发布了答案。
猜你喜欢
  • 2013-11-08
  • 2013-02-10
  • 2014-07-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-09-02
  • 2010-10-15
  • 1970-01-01
相关资源
最近更新 更多