C# 实现 IXmlSerializable 的正确方法?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/279534/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Proper way to implement IXmlSerializable?
提问by Greg
Once a programmer decides to implement IXmlSerializable
, what are the rules and best practices for implementing it? I've heard that GetSchema()
should return null
and ReadXml
should move to the next element before returning. Is this true? And what about WriteXml
- should it write a root element for the object or is it assumed that the root is already written? How should child objects be treated and written?
一旦程序员决定实施IXmlSerializable
,实施它的规则和最佳实践是什么?我听说GetSchema()
应该返回null
并且ReadXml
应该在返回之前移动到下一个元素。这是真的?那么WriteXml
- 它应该为对象编写一个根元素还是假设根元素已经被写入?应该如何处理和编写子对象?
Here's a sample of what I have now. I'll update it as I get good responses.
这是我现在拥有的示例。当我得到好的回应时,我会更新它。
public class MyCalendar : IXmlSerializable
{
private string _name;
private bool _enabled;
private Color _color;
private List<MyEvent> _events = new List<MyEvent>();
public XmlSchema GetSchema() { return null; }
public void ReadXml(XmlReader reader)
{
if (reader.MoveToContent() == XmlNodeType.Element && reader.LocalName == "MyCalendar")
{
_name = reader["Name"];
_enabled = Boolean.Parse(reader["Enabled"]);
_color = Color.FromArgb(Int32.Parse(reader["Color"]));
if (reader.ReadToDescendant("MyEvent"))
{
while (reader.MoveToContent() == XmlNodeType.Element && reader.LocalName == "MyEvent")
{
MyEvent evt = new MyEvent();
evt.ReadXml(reader);
_events.Add(evt);
}
}
reader.Read();
}
}
public void WriteXml(XmlWriter writer)
{
writer.WriteAttributeString("Name", _name);
writer.WriteAttributeString("Enabled", _enabled.ToString());
writer.WriteAttributeString("Color", _color.ToArgb().ToString());
foreach (MyEvent evt in _events)
{
writer.WriteStartElement("MyEvent");
evt.WriteXml(writer);
writer.WriteEndElement();
}
}
}
public class MyEvent : IXmlSerializable
{
private string _title;
private DateTime _start;
private DateTime _stop;
public XmlSchema GetSchema() { return null; }
public void ReadXml(XmlReader reader)
{
if (reader.MoveToContent() == XmlNodeType.Element && reader.LocalName == "MyEvent")
{
_title = reader["Title"];
_start = DateTime.FromBinary(Int64.Parse(reader["Start"]));
_stop = DateTime.FromBinary(Int64.Parse(reader["Stop"]));
reader.Read();
}
}
public void WriteXml(XmlWriter writer)
{
writer.WriteAttributeString("Title", _title);
writer.WriteAttributeString("Start", _start.ToBinary().ToString());
writer.WriteAttributeString("Stop", _stop.ToBinary().ToString());
}
}
Corresponding Sample XML
对应的示例 XML
<MyCalendar Name="Master Plan" Enabled="True" Color="-14069085">
<MyEvent Title="Write Code" Start="-8589241828854775808" Stop="-8589241756854775808" />
<MyEvent Title="???" Start="-8589241828854775808" Stop="-8589241756854775808" />
<MyEvent Title="Profit!" Start="-8589247048854775808" Stop="-8589246976854775808" />
</MyCalendar>
采纳答案by Marc Gravell
Yes, GetSchema() should return null.
是的, GetSchema()应该返回 null。
IXmlSerializable.GetSchema Method This method is reserved and should not be used. When implementing the IXmlSerializable interface, you should return a null reference (Nothing in Visual Basic) from this method, and instead, if specifying a custom schema is required, apply the XmlSchemaProviderAttribute to the class.
IXmlSerializable.GetSchema 方法 此方法是保留的,不应使用。实现 IXmlSerializable 接口时,应从此方法返回空引用(在 Visual Basic 中为 Nothing),如果需要指定自定义架构,请将 XmlSchemaProviderAttribute 应用于类。
For both read and write, the object element has already been written, so you don't need to add an outer element in write. For example, you can just start reading/writing attributes in the two.
无论是读还是写,对象元素都已经写好了,所以不需要在写的时候再添加外层元素。例如,您可以在两者中开始读/写属性。
For write:
对于写:
The WriteXml implementation you provide should write out the XML representation of the object. The framework writes a wrapper element and positions the XML writer after its start. Your implementation may write its contents, including child elements. The framework then closes the wrapper element.
您提供的 WriteXml 实现应该写出对象的 XML 表示。框架编写一个包装元素并在它开始后定位 XML 编写器。您的实现可能会编写其内容,包括子元素。然后框架关闭包装器元素。
And for read:
而对于阅读:
The ReadXml method must reconstitute your object using the information that was written by the WriteXml method.
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.
ReadXml 方法必须使用由 WriteXml 方法写入的信息重构您的对象。
调用此方法时,读取器位于为您的类型包装信息的元素的开头。也就是说,就在指示序列化对象开始的开始标记之前。当此方法返回时,它必须从头到尾读取了整个元素,包括其所有内容。与 WriteXml 方法不同,框架不会自动处理包装器元素。您的实现必须这样做。不遵守这些定位规则可能会导致代码生成意外的运行时异常或损坏的数据。
I'll agree that is a little unclear, but it boils down to "it is your job to Read()
the end-element tag of the wrapper".
我同意这有点不清楚,但归结为“Read()
包装器的结束元素标签是你的工作”。
回答by jdehaan
I wrote one article on the subject with samples as the MSDN documentation is by now quite unclear and the examples you can find on the web are most of the time incorrectly implemented.
我写了一篇关于这个主题的文章和示例,因为 MSDN 文档现在还很不清楚,你可以在网上找到的例子大部分时间都没有正确实现。
Pitfalls are handling of locales and empty elements beside what Marc Gravell already mentioned.
除了 Marc Gravell 已经提到的之外,陷阱是处理语言环境和空元素。
http://www.codeproject.com/KB/XML/ImplementIXmlSerializable.aspx
http://www.codeproject.com/KB/XML/ImplementIXmlSerializable.aspx
回答by EMP
Yes, the whole thing is a bit of a minefield, isn't it? Marc Gravell's answer pretty much covers it, but I'd like to add that in a project I worked on we found it quite awkward to have to manually write the outer XML element. It also resulted in inconsistent XML element names for objects of the same type.
是的,整个事情有点雷区,不是吗?Marc Gravell的回答几乎涵盖了它,但我想补充一点,在我参与的一个项目中,我们发现必须手动编写外部 XML 元素非常尴尬。它还导致相同类型的对象的 XML 元素名称不一致。
Our solution was to define our own IXmlSerializable
interface, derived from the system one, which added a method called WriteOuterXml()
. As you can guess, this method would simply write the outer element, then call WriteXml()
, then write the end of the element. Of course, the system XML serializer wouldn't call this method, so it was only useful when we did our own serialization, so that may or may not be helpful in your case. Similarly, we added a ReadContentXml()
method, which didn't read the outer element, only its content.
我们的解决方案是定义我们自己的IXmlSerializable
接口,从系统接口派生而来,它添加了一个名为WriteOuterXml()
. 您可以猜到,此方法将简单地写入外部元素,然后调用WriteXml()
,然后写入元素的结尾。当然,系统 XML 序列化程序不会调用此方法,因此它仅在我们进行自己的序列化时才有用,因此这对您的情况可能有帮助,也可能没有帮助。同样,我们添加了一个ReadContentXml()
方法,它不读取外部元素,只读取其内容。
回答by Thijs Dalhuijsen
If you already have an XmlDocument representation of your class or prefer the XmlDocument way of working with XML structures, a quick and dirty way of implementing IXmlSerializable is to just pass this xmldoc to the various functions.
如果您已经有您的类的 XmlDocument 表示形式,或者更喜欢使用 XmlDocument 方式处理 XML 结构,那么实现 IXmlSerializable 的一种快速而肮脏的方法是将此 xmldoc 传递给各种函数。
WARNING: XmlDocument (and/or XDocument) is an order of magnitude slower than xmlreader/writer, so if performance is an absolute requirement, this solution is not for you!
警告:XmlDocument(和/或 XDocument)比 xmlreader/writer 慢一个数量级,所以如果性能是绝对要求,这个解决方案不适合你!
class ExampleBaseClass : IXmlSerializable {
public XmlDocument xmlDocument { get; set; }
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
xmlDocument.Load(reader);
}
public void WriteXml(XmlWriter writer)
{
xmlDocument.WriteTo(writer);
}
}
回答by VoteCoffee
The interface implementation is covered by the other answers, but I wanted to toss in my 2-cents for the root element.
其他答案涵盖了接口实现,但我想为根元素投入 2 美分。
I've learned in the past to prefer putting the root element as metadata. This has a few benefits:
我在过去学会了更喜欢将根元素作为元数据。这有几个好处:
- If there is a null object, it can still serialize
- From a code readability standpoint, it makes sense
- 如果有一个空对象,它仍然可以序列化
- 从代码可读性的角度来看,这是有道理的
Below is an example of a serializable dictionary where the dictionary root element is defined in that way:
下面是一个可序列化字典的示例,其中字典根元素以这种方式定义:
using System.Collections.Generic;
[System.Xml.Serialization.XmlRoot("dictionary")]
public partial class SerializableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, System.Xml.Serialization.IXmlSerializable
{
public virtual System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public virtual void ReadXml(System.Xml.XmlReader reader)
{
var keySerializer = new System.Xml.Serialization.XmlSerializer(typeof(TKey));
var valueSerializer = new System.Xml.Serialization.XmlSerializer(typeof(TValue));
bool wasEmpty = reader.IsEmptyElement;
reader.Read();
if (wasEmpty)
return;
while (reader.NodeType != System.Xml.XmlNodeType.EndElement)
{
reader.ReadStartElement("item");
reader.ReadStartElement("key");
TKey key = (TKey)keySerializer.Deserialize(reader);
reader.ReadEndElement();
reader.ReadStartElement("value");
TValue value = (TValue)valueSerializer.Deserialize(reader);
reader.ReadEndElement();
Add(key, value);
reader.ReadEndElement();
reader.MoveToContent();
}
reader.ReadEndElement();
}
public virtual void WriteXml(System.Xml.XmlWriter writer)
{
var keySerializer = new System.Xml.Serialization.XmlSerializer(typeof(TKey));
var valueSerializer = new System.Xml.Serialization.XmlSerializer(typeof(TValue));
foreach (TKey key in Keys)
{
writer.WriteStartElement("item");
writer.WriteStartElement("key");
keySerializer.Serialize(writer, key);
writer.WriteEndElement();
writer.WriteStartElement("value");
var value = this[key];
valueSerializer.Serialize(writer, value);
writer.WriteEndElement();
writer.WriteEndElement();
}
}
public SerializableDictionary() : base()
{
}
public SerializableDictionary(IDictionary<TKey, TValue> dictionary) : base(dictionary)
{
}
public SerializableDictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer) : base(dictionary, comparer)
{
}
public SerializableDictionary(IEqualityComparer<TKey> comparer) : base(comparer)
{
}
public SerializableDictionary(int capacity) : base(capacity)
{
}
public SerializableDictionary(int capacity, IEqualityComparer<TKey> comparer) : base(capacity, comparer)
{
}
}