简体   繁体   English

反序列化实现 IXmlSerializable 的类型集合永远运行

[英]Deserializing collection of types implementing IXmlSerializable runs forever

I have a class implementing IXmlSerializable.我有一个实现 IXmlSerializable 的类。 This class contains a few properties.这个类包含一些属性。 Serializing and Deserializing a single instance of the class works fine.序列化和反序列化类的单个实例工作正常。 But in case of collection of the class, Serialization works fine but Deserialization runs forever.但是在收集类的情况下,序列化工作正常,但反序列化永远运行。 Here is a code snippet.这是一个代码片段。 I am using .Net 4.6.2.我正在使用 .Net 4.6.2。

public class MyClass : IXmlSerializable
{
    public int A { get; set; }
    public int B { get; set; }

    public XmlSchema GetSchema()
    {
        return null;
    }

    public void ReadXml(XmlReader reader)
    {
        this.A = Convert.ToInt32(reader.GetAttribute("A"));
        this.B = Convert.ToInt32(reader.GetAttribute("B"));
    }

    public void WriteXml(XmlWriter writer)
    {
        writer.WriteAttributeString("A", this.A.ToString());
        writer.WriteAttributeString("B", this.B.ToString());
    }
}
class Program
{
    static void Main(string[] args)
    {
        var instance = new MyClass { A = 1, B = 2 };
        Serialize(instance);
        instance = Deserialize<MyClass>();//works fine

        var list = new List<MyClass> { new MyClass { A = 10, B = 20 } };
        Serialize(list);
        list = Deserialize<List<MyClass>>();//runs forever
    }

    private static void Serialize(object o)
    {
        XmlSerializer ser = new XmlSerializer(o.GetType());
        using (TextWriter writer = new StreamWriter("xml.xml", false, Encoding.UTF8))
        {
            ser.Serialize(writer, o);
        }
    }

    private static T Deserialize<T>()
    {
        XmlSerializer ser = new XmlSerializer(typeof(T));
        using (TextReader reader = new StreamReader("xml.xml"))
        {
            return (T)ser.Deserialize(reader);
        }
    }
}

Your problem is that, as explained in the documentation , ReadXml() must consume its wrapper element as well as its contents:您的问题是,如文档中所述, ReadXml()必须使用其包装元素及其内容:

The ReadXml method must reconstitute your object using the information that was written by the WriteXml method. ReadXml方法必须使用WriteXml方法写入的信息重构您的对象。

When this method is called, the reader is positioned on the start tag that wraps the information for your type.调用此方法时,阅读器位于包装类型信息的开始标记上。 That is, directly on the start tag that indicates the beginning of a serialized object.也就是说,直接在指示序列化对象开始的开始标记上。 When this method returns, it must have read the entire element from beginning to end, including all of its contents.当此方法返回时,它必须从头到尾读取了整个元素,包括其所有内容。 Unlike the WriteXml method, the framework does not handle the wrapper element automatically.WriteXml方法不同,框架不会自动处理包装器元素。 Your implementation must do so.您的实现必须这样做。 Failing to observe these positioning rules may cause code to generate unexpected runtime exceptions or corrupt data.不遵守这些定位规则可能会导致代码生成意外的运行时异常或损坏的数据。

MyClass.ReadXml() is not doing this, which causes an infinite loop when the MyClass object is not serialized as the root element. MyClass.ReadXml()没有这样做,当MyClass对象未序列化为根元素时,这会导致无限循环。 Instead, your MyClass must look something like this:相反,您的MyClass必须如下所示:

public class MyClass : IXmlSerializable
{
    public int A { get; set; }
    public int B { get; set; }

    public XmlSchema GetSchema()
    {
        return null;
    }

    public void ReadXml(XmlReader reader)
    {
        /*
         * https://msdn.microsoft.com/en-us/library/system.xml.serialization.ixmlserializable.readxml.aspx
         * 
         * When this method is called, the reader is positioned at the start of the element that wraps the information for your type. 
         * That is, just before the start tag that indicates the beginning of a serialized object. When this method returns, 
         * it must have read the entire element from beginning to end, including all of its contents. Unlike the WriteXml method, 
         * the framework does not handle the wrapper element automatically. Your implementation must do so. Failing to observe these 
         * positioning rules may cause code to generate unexpected runtime exceptions or corrupt data.
         */
        var isEmptyElement = reader.IsEmptyElement;
        this.A = XmlConvert.ToInt32(reader.GetAttribute("A"));
        this.B = XmlConvert.ToInt32(reader.GetAttribute("B"));
        reader.ReadStartElement();
        if (!isEmptyElement)
        {
            reader.ReadEndElement();
        }
    }

    public void WriteXml(XmlWriter writer)
    {
        writer.WriteAttributeString("A", XmlConvert.ToString(this.A));
        writer.WriteAttributeString("B", XmlConvert.ToString(this.B));
    }
}

Now your <MyClass> element is very simple with no nested or optional elements.现在您的<MyClass>元素非常简单,没有嵌套或可选元素。 For more complex custom serializations there are a couple of strategies you could adopt to guarantee that your ReadXml() method reads exactly as much as it should, no more and no less.对于更复杂的自定义序列化,您可以采用几种策略来保证ReadXml()方法读取的内容与其应有的完全一样,不多也不少。

Firstly, you could call XNode.ReadFrom() to load the current element into an XElement .首先,您可以调用XNode.ReadFrom()将当前元素加载到XElement This requires a bit more memory than parsing directly from an XmlReader but is much easier to work with:这比直接从XmlReader解析需要更多的内存,但容易使用:

public class MyClass : IXmlSerializable
{
    public int A { get; set; }
    public int B { get; set; }

    public XmlSchema GetSchema()
    {
        return null;
    }

    public void ReadXml(XmlReader reader)
    {
        var element = (XElement)XNode.ReadFrom(reader);
        this.A = (int)element.Attribute("A");
        this.B = (int)element.Attribute("B");
    }

    public void WriteXml(XmlWriter writer)
    {
        writer.WriteAttributeString("A", XmlConvert.ToString(this.A));
        writer.WriteAttributeString("B", XmlConvert.ToString(this.B));
    }
}

Secondly, you could use XmlReader.ReadSubtree() to ensure the required XML content is consumed:其次,您可以使用XmlReader.ReadSubtree()来确保使用所需的 XML 内容:

public class MyClass : IXmlSerializable
{
    public int A { get; set; }
    public int B { get; set; }

    public XmlSchema GetSchema()
    {
        return null;
    }

    protected virtual void ReadXmlSubtree(XmlReader reader)
    {
        this.A = XmlConvert.ToInt32(reader.GetAttribute("A"));
        this.B = XmlConvert.ToInt32(reader.GetAttribute("B"));
    }

    public void ReadXml(XmlReader reader)
    {
        // Consume all child nodes of the current element using ReadSubtree()
        using (var subReader = reader.ReadSubtree())
        {
            subReader.MoveToContent();
            ReadXmlSubtree(subReader);
        }
        reader.Read(); // Consume the end element itself.
    }

    public void WriteXml(XmlWriter writer)
    {
        writer.WriteAttributeString("A", XmlConvert.ToString(this.A));
        writer.WriteAttributeString("B", XmlConvert.ToString(this.B));
    }
}

A few final notes:最后几点说明:

  • Be sure to handle both <MyClass /> and <MyClass></MyClass> .确保同时处理<MyClass /><MyClass></MyClass> These two forms are semantically identical and a sending system could chose either.这两种形式在语义上是相同的,发送系统可以选择其中一种。

  • Prefer the methods from the XmlConvert class to convert primitives from and to XML.更喜欢使用XmlConvert类中的方法将基元从 XML 转换为 XML。 Doing so handles internationalization correctly.这样做可以正确处理国际化。

  • Be sure to test with and without indentation.一定要在有和没有缩进的情况下进行测试。 Sometimes a ReadXml() method will consume an extra XML node but the bug will be hidden when indentation is enabled -- as it is the whitespace node that gets eaten.有时ReadXml()方法会消耗一个额外的 XML 节点,但是当启用缩进时该错误将被隐藏——因为它是被吃掉的空白节点。

  • For further reading see How to Implement IXmlSerializable Correctly .如需进一步阅读,请参阅如何正确实现 IXmlSerializable

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM