实现IXmlSerializable的正确方法?


153

程序员决定实施后IXmlSerializable,实施的规则和最佳做法是什么?我听说GetSchema()应该返回,null并且ReadXml应该在返回之前移至下一个元素。这是真的?怎么样WriteXml-它应该为对象编写一个根元素,还是假定该根已经被写入?应该如何对待和书写子物件?

这是我现在所拥有的样本。我会在收到良好回应后对其进行更新。

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

对应的样本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>

3
您可以向这个问题添加xml示例吗?它将使与代码一起阅读变得更加简单。谢谢!
罗里

如何处理在xml中的最后一个事件之后有XML注释等的情况。也就是说,您是否应该用一些检查您是否已读到end元素的方法来完成ReadXml()方法?当前,这假设最后一个Read()会执行此操作,但可能并非总是如此。
罗里

7
@Rory-添加示例。迟到总比不到好?
格雷格,2010年

@Greg好的信息。您是否还想让ReadXml和WriteXml使用不变文化?我认为,如果用户移至另一个国家并更改其地区和语言设置,您可能会遇到问题。在这种情况下,代码可能无法正确反序列化。我了解到,在进行序列化时始终使用不变文化是一种最佳做法
公共无线网络

Answers:


100

是的,GetSchema()应该返回null

IXmlSerializable.GetSchema方法此方法是保留的,不应使用。在实现IXmlSerializable接口时,应从此方法返回一个空引用(在Visual Basic中为Nothing),并且,如果需要指定自定义架构,则将XmlSchemaProviderAttribute应用于该类。

对于读写,object元素已经被写入,因此您无需在write中添加外部元素。例如,您可以只开始读取/写入两者中的属性。

对于

您提供的WriteXml实现应写出对象的XML表示形式。该框架将编写包装器元素,并在XML编写器启动后对其进行定位。您的实现可以编写其内容,包括子元素。然后,框架关闭包装器元素。

而对于阅读

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

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

我会同意这一点还不清楚,但是归结为“这是您Read()对包装器的end-element标签所做的工作”。


写出和阅读事件元素怎么样?手动编写起始元素感觉有些不足。我想我已经看到有人在write方法中使用XmlSerializer来写入每个子元素。
格雷格,

@格雷格; 两种用法都很好...是的,您可以根据需要使用嵌套的XmlSerializer,但这不是唯一的选择。
马克·格雷韦尔

3
感谢这些精度,MSDN中的示例代码对此毫无用处且不清楚。我被困了很多次,想知道Read / WriteXml的不对称行为。
jdehaan

1
@MarcGravell我知道这是一个旧线程。“该框架将编写一个包装器元素,并在XML编写器启动后对其进行定位。” 这就是我在努力的地方。有没有一种方法可以强制框架跳过此自动处理包装器的步骤?我有一种情况需要跳过此步骤:stackoverflow.com/questions/20885455/…– 2014
James

@詹姆斯,据我所知-Marc
Gravell

34

我写了一篇有关该主题的文章,并附带示例,因为到目前为止MSDN文档还不清楚,而且您在网络上可以找到的示例在大多数情况下都是错误实现的。

除了Marc Gravell已经提到的内容外,陷阱还用于处理语言环境和空元素。

http://www.codeproject.com/KB/XML/ImplementIXmlSerializable.aspx


优秀的文章!下次要序列化某些数据时,我一定会引用它。
格雷格,

谢谢!积极的反馈会奖励您撰写报告所花费的时间。我非常感谢您喜欢它!不要犹豫,要求批评一些观点。
jdehaan

示例比引用MSDN有用得多。

感谢您的代码项目,如果可以的话,我也投赞成票。与MSDN相比,关于属性的内容非常全面。例如,当我的:IXMLSerializable类以生成的xsd.exe为前缀时发生中断[Serializable(),XmlType(Namespace =“ MonitorService”)]。
约翰

8

是的,整个事情有点雷区,不是吗?Marc Gravell的答案几乎涵盖了这一点,但是我想补充一点,在我从事的项目中,我们发现必须手动编写外部XML元素很尴尬。还会导致相同类型的对象的XML元素名称不一致。

我们的解决方案是定义自己的 IXmlSerializable接口,该接口派生自系统一个,并添加了一个名为的方法WriteOuterXml()。如您所料,此方法将只编写外部元素,然后调用WriteXml(),然后编写元素的末尾。当然,系统XML序列化程序不会调用此方法,因此它仅在我们进行自己的序列化时才有用,因此对您的情况可能有帮助,也可能没有帮助。同样,我们添加了一个ReadContentXml()方法,该方法不读取外部元素,仅读取其内容。


5
使用C#3.0,您可能可以通过编写扩展方法来做到这一点,但这是一个有趣的想法。
马克·格雷夫

2

如果您已经具有类的XmlDocument表示形式,或者更喜欢使用XML结构的XmlDocument方式,则实现IXmlSerializable的一种快速而肮脏的方法就是将该xmldoc传递给各种功能。

警告: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);
    }
}

0

其他答案涵盖了接口实现,但是我想以2美分的价格替换root元素。

过去我了解到更喜欢将根元素用作元数据。这有一些好处:

  • 如果存在空对象,它仍然可以序列化
  • 从代码可读性的角度来看,这很有意义

以下是可序列化字典的示例,其中以这种方式定义字典根元素:

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

}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.