正确的方法来实现IXmlSerializable?

时间:2008-11-10 23:19:10

标签: c# xml xml-serialization

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

5 个答案:

答案 0 :(得分:92)

是的,GetSchema()should return null

  

IXmlSerializable.GetSchema方法这个   方法是保留的,不应该   用过的。实施时   IXmlSerializable接口,你应该   返回null引用(Nothing in   Visual Basic)来自这个方法,相反,   如果指定自定义架构是   要求,申请   XmlSchemaProviderAttribute到   类。

对于read和write,对象元素已经被写入,因此您不需要在write中添加外部元素。例如,您可以开始在两者中读/写属性。

write

  

WriteXml实现你   提供应该写出XML   对象的表示。该   框架写一个包装元素和   将XML编写器放在其后面   开始。您的实施可能会写   其内容,包括儿童   元素。然后框架关闭   包装元素。

对于read

  

ReadXml方法必须重新构建   你的对象使用的信息   是由WriteXml方法编写的。

     

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

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

答案 1 :(得分:32)

我用样本写了一篇关于这个主题的文章,因为MSDN文档到目前为止还不太清楚,你可以在网上找到的例子大部分都是错误实现的。

陷阱是处理Marc Gravell已经提到的区域和空元素。

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

答案 2 :(得分:8)

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

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

答案 3 :(得分: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);
    }
}

答案 4 :(得分:0)

其他答案涵盖了接口实现,但我想以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)
    {
    }

}