反序列化实现 IXmlSerializable 的类型集合永远运行 [英] Deserializing collection of types implementing IXmlSerializable runs forever

查看:37
本文介绍了反序列化实现 IXmlSerializable 的类型集合永远运行的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个实现 IXmlSerializable 的类.这个类包含一些属性.序列化和反序列化类的单个实例工作正常.但是在收集类的情况下,序列化工作正常,但反序列化永远运行.这是一个代码片段.我使用的是 .Net 4.6.2.

I have a class implementing 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.

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

推荐答案

你的问题是,正如 文档ReadXml() 必须使用其包装元素及其内容:

Your problem is that, as explained in the documentation, ReadXml() must consume its wrapper element as well as its contents:

ReadXml 方法必须使用由 WriteXml 方法写入的信息重构您的对象.

The ReadXml method must reconstitute your object using the information that was written by the WriteXml method.

当这个方法被调用时,阅读器被定位在包装你的类型信息的开始标签上.也就是说,直接在指示序列化对象开始的开始标记上.当这个方法返回时,它必须从头到尾读取了整个元素,包括它的所有内容.与 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. Your implementation must do so. Failing to observe these positioning rules may cause code to generate unexpected runtime exceptions or corrupt data.

MyClass.ReadXml() 没有这样做,当 MyClass 对象没有序列化为根元素时,这会导致无限循环.相反,您的 MyClass 必须如下所示:

MyClass.ReadXml() is not doing this, which causes an infinite loop when the MyClass object is not serialized as the root element. Instead, your MyClass must look something like this:

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

现在您的 元素非常简单,没有嵌套或可选元素.对于更复杂的自定义序列化,您可以采用几种策略来保证您的 ReadXml() 方法读取的内容与其应有的一样多,不多也不少.

Now your <MyClass> element is very simple with no nested or optional elements. 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.

首先,您可以调用XNode.ReadFrom() 将当前元素加载到 XElement 中.这比直接从 XmlReader 解析需要更多的内存,但 更容易使用:

Firstly, you could call XNode.ReadFrom() to load the current element into an XElement. This requires a bit more memory than parsing directly from an XmlReader but is much easier to work with:

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

其次,您可以使用 XmlReader.ReadSubtree() 以确保使用所需的 XML 内容:

Secondly, you could use XmlReader.ReadSubtree() to ensure the required XML content is consumed:

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

最后几点:

  • 确保同时处理.这两种形式在语义上是相同的,发送系统可以选择其中一种.

  • Be sure to handle both <MyClass /> and <MyClass></MyClass>. These two forms are semantically identical and a sending system could chose either.

首选中的方法XmlConvert 类用于将原语从 XML 转换为 XML.这样做可以正确处理国际化.

Prefer the methods from the XmlConvert class to convert primitives from and to XML. Doing so handles internationalization correctly.

请务必使用和不使用缩进进行测试.有时 ReadXml() 方法会消耗一个额外的 XML 节点,但是当启用缩进时该错误将被隐藏——因为它是被吃掉的空白节点.

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.

有关进一步阅读,请参阅如何正确实现 IXmlSerializable.

For further reading see How to Implement IXmlSerializable Correctly.

这篇关于反序列化实现 IXmlSerializable 的类型集合永远运行的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆