您的问题是,正如documentation 中所述,ReadXml() 必须使用其包装元素及其内容:
ReadXml 方法必须使用WriteXml 方法写入的信息重构您的对象。
调用此方法时,阅读器将定位在包装您的类型信息的开始标记上。也就是说,直接在指示序列化对象开始的开始标记上。 当这个方法返回时,它必须从头到尾读取了整个元素,包括它的所有内容。与WriteXml 方法不同,框架不会自动处理包装器元素。您的实施必须这样做。不遵守这些定位规则可能会导致代码生成意外的运行时异常或损坏的数据。
MyClass.ReadXml() 没有这样做,当MyClass 对象未序列化为根元素时会导致无限循环。相反,您的 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));
}
}
现在您的<MyClass> 元素非常简单,没有嵌套或可选元素。对于更复杂的自定义序列化,您可以采用几种策略来保证您的 ReadXml() 方法读取的内容与应有的一样多,不多也不少。
首先,您可以调用XNode.ReadFrom() 将当前元素加载到XElement 中。这比直接从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));
}
}
其次,您可以使用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));
}
}
最后几点说明:
确保同时处理<MyClass /> 和<MyClass></MyClass>。这两种形式在语义上是相同的,发送系统可以选择其中一种。
首选 XmlConvert 类中的方法将原语与 XML 相互转换。这样做可以正确处理国际化。
请务必在有和没有缩进的情况下进行测试。有时ReadXml() 方法会消耗一个额外的 XML 节点,但是当启用缩进时该错误将被隐藏——因为它是被吃掉的空白节点。
如需进一步阅读,请参阅How to Implement IXmlSerializable Correctly。