繁体   English   中英

实现 IXmlSerializable 的正确方法?

[英]Proper way to implement IXmlSerializable?

一旦程序员决定实现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>

是的, GetSchema() 应该返回 null

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

无论是读还是写,对象元素都已经写好了,所以不需要在写的时候再添加外层元素。 例如,您可以在两者中开始读/写属性。

对于

您提供的 WriteXml 实现应该写出对象的 XML 表示。 框架编写一个包装元素并在它开始后定位 XML 编写器。 您的实现可能会编写其内容,包括子元素。 然后框架关闭包装器元素。

而对于阅读

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

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

我同意这有点不清楚,但归结为“ Read()包装器的结束元素标签是你的工作”。

我写了一篇关于这个主题的文章和示例,因为 MSDN 文档现在还很不清楚,你可以在网上找到的例子大部分时间都没有正确实现。

除了 Marc Gravell 已经提到的之外,陷阱是处理语言环境和空元素。

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

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

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

如果您已经拥有您的类的 XmlDocument 表示形式,或者更喜欢使用 XmlDocument 方式处理 XML 结构,那么实现 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);
    }
}

其他答案涵盖了接口实现,但我想为根元素投入 2 美分。

我在过去学会了更喜欢将根元素作为元数据。 这有几个好处:

  • 如果有一个空对象,它仍然可以序列化
  • 从代码可读性的角度来看,这是有道理的

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

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

}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM