我有一个实现IXmlSerializable的类。该类包含一些属性。序列化和反序列化该类的单个实例工作正常。但是在收集类的情况下,序列化工作正常,但反序列化永远运行。这是一段代码片段。我使用的是.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);
}
}
}
答案 0 :(得分:1)
您的问题是,正如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。