Stackoverflow Exception when serializing class

给你一囗甜甜゛ 提交于 2019-12-24 02:33:19

问题


I have a tree and want to serialize them to xml. The nodes derive from a Nodebase class (found here I think), which fails on serializing.

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 ==
}

First the serializer advises that an IEnumerable can only be serialized, when a function Add(System.Object) exists. Why?

I add a dummy function public void Add(object node) { }

and tried to serialize. I get a Stackoverflow exception then. Why, there is nothing special in this class. What I´m doing wrong?

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

回答1:


You are running into multiple problems with XmlSerializer.

Firstly, XmlSerializer makes a distinction between serializing a collection and a regular object. When serializing a collection, only the items in the collection are serialized, not the properties of the collection class itself. Otherwise if the class is not a collection the properties will be serialized. This is spelled out in the documentation:

Items That Can Be Serialized

The following items can be serialized using the XmLSerializer class:

  • Public read/write properties and fields of public classes.

  • Classes that implement ICollection or IEnumerable.

    Note: Only collections are serialized, not public properties.

  • XmlElement objects.

  • XmlNode objects.

  • DataSet objects.

Your NodeBase class functions simultaneously as a node and an IEnumerable of child nodes. Because of this, XmlSerializer won't serialize any properties of derived classes, which is probably not what you want. Instead, you need to extract a separate Children property, and only enumerate & serialize using that.

(Incidentally, making NodeBase implement IEnumerable<NodeBase> is somehow causing the constructor for XmlSerializer to overflow the stack. That surprises me -- but even if that didn't happen, your code would not work as expected.)

Secondly, even if you serialize the children via a child property, you will encounter yet another infinite recursion. That's because XmlSerializer is a tree serializer not a graph serializer. The difference is as follows:

  1. A graph serializer such as BinaryFormatter recursively descends the object graph starting from the root object being serialized. The first time it encounters an object, it serializes it in a table, generates a temporary ID for it, and serializes the ID in the container class. If the serializer encounters the same object subsequently, it looks it up in the table and simply stores the run-time ID again.

    Thus, cyclic object graphs and graphs where nodes are referenced multiple times can be serialized.

  2. A tree serializer such as XmlSerializer is more limited. It recursively descends the object graph starting from the root object being serialized and serializes each object as it is encountered. If it encounters the same object twice, it will serialize it twice. If it encounters a cycle in the object graph, it will fall into infinite recursion. That's because it expects and requires the object hierarchy to be a pure tree.

So, in your structure, you have:

public class NodeBase 
{

    public NodeBase Parent { get; private set; }

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

Both these properties are public and thus serializable. So, the root node will recursively serialize its first child, and the Parent property will recursively serialize the parent.

To fix this, mark all the NodeBase-related properties other than the Children as [XmlIgnore]. You'll also need to serialize the children explicitly as an array using a proxy property:

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

This will allow your tree to be serialized and deserialized.

(Incidentally, making a class T implement IEqualityComparer<T> is very atypical. Usually it implements IEquatable<T> and/or some separate comparer class implements IEqualityComparer<T>.)



来源:https://stackoverflow.com/questions/26575354/stackoverflow-exception-when-serializing-class

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!