【问题标题】:Proper way to implement IXmlSerializable?实现 IXmlSerializable 的正确方法?
【发布时间】:2010-09-21 17:27:11
【问题描述】:

一旦程序员决定实现IXmlSerializable,实现它的规则和最佳实践是什么?我听说GetSchema() 应该返回nullReadXml 应该在返回之前移动到下一个元素。这是真的?那么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>

【问题讨论】:

  • 您能在这个问题中添加一个 xml 示例吗?这将使与代码一起阅读变得更简单。谢谢!
  • 如何处理在您的 xml 中的最后一个事件之后有 XML 注释等的情况。即,您是否应该使用检查您是否通读到结束元素的内容来完成 ReadXml() 方法?目前这假设最后一个 Read() 会这样做,但可能并不总是这样。
  • @Rory - 添加了示例。迟到总比没有好?
  • @Greg 好信息。难道您不想让 ReadXml 和 WriteXml 使用不变文化吗?我认为如果用户搬到另一个国家并更改了他们的区域和语言设置,您可能会遇到问题。在这种情况下,代码可能无法正确反序列化。我读过,在进行序列化时始终使用不变文化是最佳实践

标签: c# xml xml-serialization


【解决方案1】:

是的,GetSchema() should return null

IXmlSerializable.GetSchema 方法 This 方法是保留的,不应该是 用过的。实施时 IXmlSerializable 接口,你应该 返回一个空引用(没有 Visual Basic) 从这个方法,而是, 如果指定自定义架构是 需要,应用 XmlSchemaProviderAttribute 到 类。

对于read和write来说,object元素已经被写入了,所以你不需要在write中添加外部元素。例如,您可以在两者中开始读取/写入属性。

对于write

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

对于read

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

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

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

【讨论】:

  • 写出和读取事件元素怎么样?手动编写起始元素感觉很骇人听闻。我想我见过有人在 write 方法中使用 XmlSerializer 来编写每个子元素。
  • @Greg;两种用法都可以……是的,如果需要,您可以使用嵌套的 XmlSerializer,但这不是唯一的选择。
  • 感谢这些精确度,MSDN 中的示例代码对此非常无用且不清楚。我被卡住了很多次,想知道 Read/WriteXml 的不对称行为。
  • @MarcGravell 我知道这是一个旧线程。 “框架编写一个包装器元素并在 XML 编写器启动后定位它。”这就是我挣扎的地方。有没有办法强制框架跳过自动处理包装器的这一步?我有一种情况需要跳过这一步:stackoverflow.com/questions/20885455/…
  • @James 据我所知不是
【解决方案2】:

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

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

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

【讨论】:

  • 优秀的文章!下次我要序列化一些数据时一定会参考它。
  • 谢谢!积极反馈的数量奖励了投入编写它的时间。我非常感谢你喜欢它!不要犹豫,要求批评一些观点。
  • 示例比引用 MSDN 有用得多。
  • 感谢代码项目,如果可以的话,我也会投赞成票。与 MSDN 相比,属性方面的内容非常全面。例如,我的 : IXMLSerializable 类在以 xsd.exe 生成 [Serializable(), XmlType(Namespace = "MonitorService")] 为前缀时中断。
【解决方案3】:

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

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

【讨论】:

  • 使用 C# 3.0,您可能可以通过编写扩展方法来做到这一点,但这是一个有趣的想法。
【解决方案4】:

如果您已经拥有类的 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);
    }
}

【讨论】:

    【解决方案5】:

    其他答案涵盖了接口实现,但我想为根元素投入 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)
        {
        }
    
    }
    

    【讨论】:

      猜你喜欢
      • 2023-03-11
      • 2010-10-28
      • 2019-04-30
      • 1970-01-01
      • 2011-12-03
      • 1970-01-01
      • 2017-10-17
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多