XmlSerializer在读取后中止读取。读取()

时间:2018-04-26 16:06:16

标签: c# .net xml-serialization xmlserializer

为什么XmlSerializer在使用读卡器时会中止读取? enter image description here

示例XML - here -
下载示例项目- here -

使用XmlSerializer时,可以对类进行序列化和反序列化。

var serializer = new XmlSerializer(typeof(MainItem));
using (var reader = new StreamReader(SettingFile.FullName))
{
    var deserializedObject = serializer.Deserialize(reader);
    ret = (MainItem)deserializedObject;
}

要序列化的模型

public class MainItem
{
    public List<Child1> Child1{ get; set; }
}

ConnectionModel我有一个自定义类,我从XmlSerializer中获取XmlReader以自定义方式反序列化。这是通过向我的班级实现接口IXmlSerializable来完成的。

public XmlSchema GetSchema()
{
    return null;
}


public void ReadXml(XmlReader reader)
{

    reader.Read(); // <-- as soon as I comment this out, the serializer will finish proper!
}



public void WriteXml(XmlWriter writer)
{
    writer.WriteStartElement(nameof(PropertyInfo.Name));
    writer.WriteString(Value.Name);
    writer.WriteEndElement();

    writer.WriteStartElement(nameof(PropertyInfo.PropertyType));
    writer.WriteString(Value.PropertyType.Name);
    writer.WriteEndElement();

    writer.WriteStartElement(XML_PropertyTypeFullName);
    writer.WriteString(Value.PropertyType.FullName);
    writer.WriteEndElement();

    writer.WriteStartElement(nameof(PropertyInfo.CanRead));
    writer.WriteString(Value.CanRead.ToString());
    writer.WriteEndElement();

    writer.WriteStartElement(nameof(PropertyInfo.CanWrite));
    writer.WriteString(Value.CanWrite.ToString());
    writer.WriteEndElement();

}

reader.Read() 根本不会抛出任何异常。并且它正确地将值读入模型,但是为了测试,我评论了全部,除了这一行reader.Read()。它在调试窗口中显示文件中间的?XmlTextReader(reader).LineNumber,而不是EOF。如果reader.Read()正在使用(未注释掉),则会读取一个项目而不是下一个项目。

使用System.Xml.Serialization.XmlSerializer时使用IXmlSerializable时是否需要关注什么?

1 个答案:

答案 0 :(得分:1)

下载项目后,我可以在此处创建接近Minimal, Complete and Verifiable example的内容:https://dotnetfiddle.net/OvPQ6J。虽然不会抛出异常,但会跳过大量的XML文件,导致<ChildItems>集合缺少条目。

问题是,ReadXml()并未按照documentation中的要求推进XmlReader超过相应元素的结尾(重点已添加):

  

ReadXml方法必须使用WriteXml方法编写的信息重新构建对象。

     

调用此方法时,阅读器位于包含类型信息的开始标记上。也就是说,直接在指示序列化对象开始的开始标记上。当此方法返回时,它必须从头到尾读取整个元素,包括其所有内容。与WriteXml方法不同,框架不会自动处理包装元素。您的实施必须这样做。 未能遵守这些定位规则可能会导致代码生成意外的运行时异常或损坏数据。

因此,意外的随机异常是错误实施ReadXml()的可能后果。有关更多信息,请参阅 Proper way to implement IXmlSerializable? How to Implement IXmlSerializable Correctly

由于很容易犯这个错误,你可以通过调用ReadSubtree()或者用XNode.ReadFrom()将整个XML加载到内存中来系统地避免这种错误。使用以下两个基类之一:

public abstract class StreamingXmlSerializableBase : IXmlSerializable
{
    // Populate the object with the XmlReader returned by ReadSubtree
    protected abstract void Populate(XmlReader reader);

    public XmlSchema GetSchema() => null;

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

    public abstract void WriteXml(XmlWriter writer);
}

public abstract class XmlSerializableBase : IXmlSerializable
{
    // Populate the object with an XElement loaded from the XmlReader for the current node
    protected abstract void Populate(XElement element);

    public XmlSchema GetSchema() => null;

    public void ReadXml(XmlReader reader)
    {
        reader.MoveToContent();
        var element = (XElement)XNode.ReadFrom(reader);
        Populate(element);
    }   

    public abstract void WriteXml(XmlWriter writer);
}

这是SerializableClass使用XmlSerializableBase的固定版本:

public class SerializableClass : XmlSerializableBase
{
    public string Title { get; set; } = "Test title";
    public string Description { get; set; } = "Super description";
    public int Number { get; set; } = (int)(DateTime.Now.Ticks % 99);

    protected override void Populate(XElement element)
    {
        this.Title = (string)element.Element(nameof(this.Title));
        this.Description = (string)element.Element(nameof(this.Description));
        // Leave Number unchanged if not present in the XML
        this.Number = (int?)element.Element(nameof(this.Number)) ?? this.Number;  
    }

    public override void WriteXml(XmlWriter writer)
    {
        writer.WriteStartElement(nameof(this.Title));
        writer.WriteString(this.Title);
        writer.WriteEndElement();

        writer.WriteStartElement(nameof(this.Description));
        writer.WriteString(this.Description);
        writer.WriteEndElement();

        writer.WriteStartElement(nameof(this.Number));
        // Do not use ToString() as it is locale-dependent.  
        // Instead use XmlConvert.ToString(), or just writer.WriteValue
        writer.WriteValue(this.Number);
        writer.WriteEndElement();
    }
}

注意:

  • 在原始代码中,使用Number将整数ToString()作为字符串写入XML:

    writer.WriteString(this.Number.ToString());  
    

    这可能会导致问题,因为ToString()的返回可能与语言环境有关。而是使用XmlConvert.ToString(Int32)XmlWriter.WriteValue(Int32)

  • XmlReader.ReadSubtree()XmlReader位于位于正在读取的元素的EndElement节点上,而XNode.ReadFrom()使读者处于定位位置紧跟正在读取的元素的EndElement节点之后。这说明Read()中对StreamingXmlSerializableBase.ReadXml()的额外电话。

  • 使用XmlReader手动读取XML的代码应始终使用格式化和未格式化的XML进行单元测试,因为某些错误只会出现在一个或另一个错误中。 (例如,请参阅this answerthis one also

示例工作.Net小提琴:https://dotnetfiddle.net/s9OJOQ