【问题标题】:Stackoverflow Exception when serializing class序列化类时的Stackoverflow异常
【发布时间】:2014-10-26 16:41:57
【问题描述】:

我有一棵树,想将它们序列化为 xml。节点派生自 Nodebase 类(我认为可以在此处找到),该类在序列化时失败。

public class NodeBase : IEqualityComparer, IEnumerable, IEnumerable<NodeBase>
{

    public NodeBase Parent { get; private set; }

    private readonly IList<NodeBase> children = new ObservableCollection<NodeBase>();

    public NodeBase this[int index]
    {
        get
        {
            return this.children[index];
        }
    }

    public void AddChild(NodeBase childNode, int index = -1)
    {
        if (index < -1)
        {
            throw new ArgumentException("The index can not be lower then -1");
        }
        if (index > this.Children.Count() - 1)
        {
            throw new ArgumentException("The index ({0}) can not be higher then index of the last iten. Use the AddChild() method without an index to add at the end".FormatInvariant(index));
        }
        if (!childNode.IsRoot)
        {
            throw new ArgumentException("The child node with value [{0}] can not be added because it is not a root node.".FormatInvariant(childNode.ToString()));
        }

        if (this.Root == childNode)
        {
            throw new ArgumentException("The child node with value [{0}] is the rootnode of the parent.".FormatInvariant(childNode.ToString()));
        }

        if (childNode.SelfAndDescendants.Any(n => this == n))
        {
            throw new ArgumentException("The childnode with value [{0}] can not be added to itself or its descendants.".FormatInvariant(childNode.ToString()));
        }

        childNode.Parent = this;
        if (index == -1)
        {
            this.children.Add(childNode);
        }
        else
        {
            this.children.Insert(index, childNode);
        }
    }

    public void AddChildren(params NodeBase[] childNodes)
    {
        foreach (var childNode in childNodes)
        {
            this.AddChild(childNode);
        }
    }

    public bool RemoveChild(NodeBase node)
    {
        return this.children.Remove(node);
    }

    public void AddFirstChild(NodeBase childNode)
    {
        this.AddChild(childNode, 0);
    }

    public void AddFirstSibling(NodeBase childNode)
    {
        this.Parent.AddFirstChild(childNode);
    }

    public void AddLastSibling(NodeBase childNode)
    {
        this.Parent.AddChild(childNode);
    }

    public IEnumerable<NodeBase> Leaves
    {
        get
        {
            return this.Descendants.Where(n => !n.Children.Any());
        }
    }

    public void AddParent(NodeBase parentNode)
    {
        if (!this.IsRoot)
        {
            throw new ArgumentException("This node [{0}] already has a parent".FormatInvariant(this.ToString()), "parentNode");
        }
        parentNode.AddChild(this);
    }

    public IEnumerable<NodeBase> Ancestors
    {
        get
        {
            if (this.IsRoot)
            {
                return Enumerable.Empty<NodeBase>();
            }
            return this.Parent.ToIEnumerable().Concat(this.Parent.Ancestors);
        }
    }

    public IEnumerable<NodeBase> Descendants
    {
        get
        {
            return this.SelfAndDescendants.Skip(1);
        }
    }

    public IEnumerable<NodeBase> Children
    {
        get
        {
            return this.children;
        }
    }

    public IEnumerable<NodeBase> Siblings
    {
        get
        {
            return this.SelfAndSiblings.Where(Other);
        }
    }

    private bool Other(NodeBase node)
    {
        return !ReferenceEquals(node, this);
    }

    public IEnumerable<NodeBase> SelfAndChildren
    {
        get
        {
            return this.ToIEnumerable().Concat(Children);
        }
    }

    public IEnumerable<NodeBase> SelfAndAncestors
    {
        get
        {
            return this.ToIEnumerable().Concat(Ancestors);
        }
    }

    public IEnumerable<NodeBase> SelfAndDescendants
    {
        get
        {
            return this.ToIEnumerable().Concat(this.Children.SelectMany(c => c.SelfAndDescendants));
        }
    }

    public IEnumerable<NodeBase> SelfAndSiblings
    {
        get
        {
            if (this.IsRoot)
            {
                return this.ToIEnumerable();
            }

            return this.Parent.Children;
        }
    }

    public NodeBase GetPreviousSibling()
    {
        return this.GetPreviousSibling(this);
    }

    public NodeBase GetPreviousSibling(NodeBase node)
    {
        if (this.Parent == null)
        {
            return null;
        }
        var previousNode = this.Parent.Children.Reverse().SkipWhile(i => !i.Equals(node))
                                           .Skip(1)
                                           .FirstOrDefault();
        return previousNode;
    }

    public NodeBase GetPreviousNode()
    {
        var previousSibling = this.GetPreviousSibling();
        if (previousSibling != null)
        {
            if (this.HasChildren)
            {
                NodeBase current = this;
                while (true)
                {
                    var child = current.Children.Last();
                    if (!child.HasChildren)
                    {
                        return child;
                    }
                    else
                    {
                        current = child;
                    }
                }
            }
            else
            {
                return previousSibling;
            }
        }
        else
        {
            if (this.HasParent)
            {
                return this.Parent;
            }
            else
            {
                return null;
            }
        }
    }

    public NodeBase GetNextNode()
    {
        if (this.HasChildren)
        {
            return this.Children.First();
        }
        else
        {
            var nextSibling = this.GetNextSibling();
            if (nextSibling != null)
            {
                return nextSibling;
            }
            else
            {
                NodeBase current = this;
                NodeBase parent;
                while (true)
                {
                    parent = current.Parent;
                    if (parent == null)
                        return null;
                    else
                    {
                        var nextSibling2 = parent.GetNextSibling();
                        if (nextSibling2 != null)
                        {
                            return nextSibling2;
                        }
                        else
                        {
                            current = parent;
                        }
                    }
                }
            }
        }
    }

    public bool HasParent
    {
        get { return this.Parent != null; }
    }

    public bool HasChildren
    {
        get
        {
            return this.children.Any();
        }
    }

    public NodeBase GetNextSibling()
    {
        return this.GetNextSibling(this);
    }

    public NodeBase GetNextSibling(NodeBase node)
    {
        if (this.Parent == null)
        {
            return null;
        }

        var foundNode = this.Parent.Children.SkipWhile(i => !i.Equals(node));
        var nextNode = foundNode.Skip(1)
                                .FirstOrDefault();
        return nextNode;
    }

    public IEnumerable<NodeBase> All
    {
        get
        {
            return this.Root.SelfAndDescendants;
        }
    }

    public IEnumerable<NodeBase> SameLevel
    {
        get
        {
            return this.SelfAndSameLevel.Where(Other);
        }
    }

    public int Level
    {
        get
        {
            return this.Ancestors.Count();
        }
    }

    public IEnumerable<NodeBase> SelfAndSameLevel
    {
        get
        {
            return this.GetNodesAtLevel(Level);
        }
    }

    public IEnumerable<NodeBase> GetNodesAtLevel(int level)
    {
        return this.Root.GetNodesAtLevelInternal(level);
    }

    private IEnumerable<NodeBase> GetNodesAtLevelInternal(int level)
    {
        if (level == this.Level)
        {
            return this.ToIEnumerable();
        }
        return this.Children.SelectMany(c => c.GetNodesAtLevelInternal(level));
    }

    public NodeBase Root
    {
        get
        {
            return this.SelfAndAncestors.Last();
        }
    }

    public void Disconnect()
    {
        if (this.IsRoot)
        {
            throw new InvalidOperationException("The root node [{0}] can not get disconnected from a parent.".FormatInvariant(this.ToString()));
        }
        this.Parent.children.Remove(this);
        this.Parent = null;
    }

    public bool IsRoot
    {
        get
        {
            return this.Parent == null;
        }
    }

    public void Traverse(Action<NodeBase> action)
    {
        action(this);
        foreach (var child in children)
        {
            child.Traverse(action);
        }
    }

    public IEnumerable<NodeBase> Flatten()
    {
        return new[] { this }.Union(children.SelectMany(x => x.Flatten()));
    }

    IEnumerator<NodeBase> IEnumerable<NodeBase>.GetEnumerator()
    {
        return this.children.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.children.GetEnumerator();
    }

    public IEnumerator<NodeBase> GetEnumerator()
    {
        return this.children.GetEnumerator();
    }

    private static bool IsSameId<TId>(TId id, TId? parentId)
        where TId : struct
    {
        return parentId != null && id.Equals(parentId.Value);
    }

    #region Equals en ==

    public static bool operator ==(NodeBase value1, NodeBase value2)
    {
        if ((object)(value1) == null && (object)value2 == null)
        {
            return true;
        }
        return ReferenceEquals(value1, value2);
    }

    public static bool operator !=(NodeBase value1, NodeBase value2)
    {
        return !(value1 == value2);
    }

    public override bool Equals(Object anderePeriode)
    {
        var valueThisType = anderePeriode as NodeBase;
        return this == valueThisType;
    }

    public bool Equals(NodeBase value)
    {
        return this == value;
    }

    public bool Equals(NodeBase value1, NodeBase value2)
    {
        return value1 == value2;
    }

    bool IEqualityComparer.Equals(object value1, object value2)
    {
        var valueThisType1 = value1 as NodeBase;
        var valueThisType2 = value2 as NodeBase;

        return Equals(valueThisType1, valueThisType2);
    }

    public int GetHashCode(object obj)
    {
        return GetHashCode(obj as NodeBase);
    }

    public override int GetHashCode()
    {
        return GetHashCode(this);
    }

    public int GetHashCode(NodeBase value)
    {
        return base.GetHashCode();
    }

    #endregion Equals en ==
}

首先,序列化程序建议只有在函数 Add(System.Object) 存在时才能序列化 IEnumerable。为什么?

我添加了一个虚拟函数 公共无效添加(对象节点) { }

并尝试序列化。然后我得到一个 Stackoverflow 异常。 为什么,这门课没有什么特别的。我做错了什么?

 public string SerializeToString<T>(T objectInstance)
{
var xmlSerializer = new XmlSerializer(typeof(T));
var xml = new StringBuilder();

using (TextWriter writer = new StringWriter(xml))
{
    xmlSerializer.Serialize(writer, objectInstance);
}

return xml.ToString();
}

【问题讨论】:

  • XmlSerializer 只会序列化公共属性而不是私有属性...
  • 你的虚拟 Add() 无论如何都不会擅长反序列化。
  • 从异常中获取任何信息?
  • 一个 OT,但我很确定你可以(应该)删除所有 Equals 和 GethashCode() 的东西。返回基类行为的方法似乎很复杂(并且可能存在缺陷)。
  • 如果它有用,这里是我开始工作的代码版本:pastebin.com/nXT0fxw6。祝你好运!

标签: c# exception xmlserializer


【解决方案1】:

XmlSerializer 遇到了多个问题。

首先,XmlSerializer 区分了序列化 collection 和常规 object。序列化集合时,仅序列化集合中的项目,而不序列化集合类本身的属性。否则,如果类不是集合,则属性将被序列化。这在documentation 中有详细说明:

可以序列化的项目

可以使用 XmlSerializer 类对以下项目进行序列化:

  • 公共类的公共读/写属性和字段。

  • 实现 ICollection 或 IEnumerable 的类。

    注意: 只有集合被序列化,而不是公共属性。

  • XmlElement 对象。

  • XmlNode 对象。

  • 数据集对象。

您的NodeBase 类同时用作节点​​和子节点的 IEnumerable。因此,XmlSerializer 不会序列化派生类的任何属性,这可能不是您想要的。相反,您需要提取一个单独的 Children 属性,并仅使用该属性进行枚举和序列化。

(顺便说一句,使NodeBase 实现IEnumerable&lt;NodeBase&gt; 会以某种方式导致XmlSerializer 的构造函数溢出堆栈。这让我感到惊讶——但即使没有发生这种情况,您的代码也不会按预期工作。 )

其次,即使您通过子属性序列化子级,您也会遇到另一个无限递归。那是因为XmlSerializer 是一个树序列化器而不是一个图形序列化器。区别如下:

  1. 图序列化器,例如BinaryFormatter,从被序列化的根对象开始递归地下降对象图。第一次遇到对象时,将其序列化到一个表中,为其生成一个临时ID,并在容器类中序列化该ID。如果序列化程序随后遇到相同的对象,它会在表中查找它并再次存储运行时 ID。

    因此,循环对象图和节点被多次引用的图可以被序列化。

  2. 诸如XmlSerializer 之类的树序列化程序 受到更多限制。它从被序列化的根对象开始递归地下降对象图,并在遇到每个对象时对其进行序列化。如果它两次遇到同一个对象,它会将它序列化两次。如果在对象图中遇到循环,会陷入无限递归。这是因为它期望并要求对象层次结构是纯 tree

所以,在你的结构中,你有:

public class NodeBase 
{

    public NodeBase Parent { get; private set; }

    public IEnumerable<NodeBase> Children
    {
        get
        {
            return this.children;
        }
    }
}

这两个属性都是公共的,因此是可序列化的。因此,根节点将递归序列化其第一个子节点,Parent 属性将递归序列化父节点

要解决此问题,请将除Children 之外的所有与NodeBase 相关的属性标记为[XmlIgnore]。您还需要使用代理属性将子项显式序列化为数组:

public class NodeBase 
{
    [XmlIgnore]
    public NodeBase Parent { get; private set; }

    [XmlIgnore]
    public IEnumerable<NodeBase> Children
    {
        get
        {
            return this.children;
        }
    }

    [XmlArray("Children"), Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
    public NodeBase [] ChildList
    {
        get
        {
            return children.ToArray();
        }
        set
        {
            if (!object.ReferenceEquals(value, this.children))
            {
                children.Clear();
                foreach (var child in value)
                    AddChild(child);
            }
        }
    }
}

这将允许您的树被序列化和反序列化。

(顺便说一句,使T 类实现IEqualityComparer&lt;T&gt; 是非常不典型的。通常它实现IEquatable&lt;T&gt; 和/或一些单独的比较器类实现IEqualityComparer&lt;T&gt;。)

【讨论】:

  • 您好,我不在家,因此无法在此期间回答。感谢您的详细回答,我删除了 IEnumerable、GetEnumerator() Equals 并将除 ChildList 之外的所有属性标记为 [XmlIgnore]然后它起作用了,但我失去了枚举的能力。我希望他能处理递归。你是怎么发现问题的?我讨厌 xml 序列化程序,因为他几乎什么都不能处理 - 没有接口,没有对象,因为我现在听说他无法检测到递归。有更好的替代方案吗?
  • @JohnnyBravo75 - 我有第二次递归的个人经验 - 用循环数据结构溢出堆栈。当我检查以确保用[XmlIgnore] 标记东西时,我找到了第一个问题,但发现它没有。
  • 我试图注释掉属性,但没有帮助。我使用了很多 wcf 服务,它们使用了 xmlserializer,并且我通过网络发送了很多树,并且从未遇到过序列化问题,所以我很惊讶并且不认为递归是原因。 wcf xml 序列化程序和这个序列化程序之间有区别吗?编辑:不,我错了,我在 wcf 服务中使用了二进制序列化程序。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-10-09
  • 2011-12-25
  • 2017-11-07
  • 1970-01-01
  • 2016-10-08
  • 2011-09-03
  • 2012-06-21
相关资源
最近更新 更多