Правильный способ реализации 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>

Ответ 1

Да, GetSchema() должен вернуть значение null.

IXmlSerializable.GetSchema Method Это метод зарезервирован и не должен быть используемый. При реализации IXmlSerializable интерфейс, вы должны вернуть нулевую ссылку (Nothing in Visual Basic) из этого метода, а вместо этого, если указать настраиваемую схему требуется применять XmlSchemaProviderAttribute для класс.

Для чтения и записи элемент объекта уже написан, поэтому вам не нужно добавлять внешний элемент в запись. Например, вы можете просто начать чтение/запись атрибутов в двух.

Для write:

Реализация WriteXml вы предоставить должен выписать XML представление объекта. framework пишет элемент оболочки и позиционирует XML-писатель после Начало. Ваша реализация может написать его содержание, включая ребенка элементы. Затем структура закрывается элемент оболочки.

И для read:

Метод ReadXml должен восстанавливаться ваш объект, используя информацию, которая был написан методом WriteXml.

Когда этот метод вызывается, читатель позиционируется в начале элемент, который обертывает информацию для ваш тип. То есть, перед начальный тег, указывающий начало сериализованного объекта. Когда это метод возвращает, он должен был прочитать весь элемент от начала до конца, включая все его содержимое. В отличие от метод WriteXml, структура не обрабатывает элемент оболочки автоматически. Ваша реализация должен сделать это. Несоблюдение этих правила позиционирования могут привести к генерировать неожиданные исключения во время выполнения или поврежденных данных.

Я соглашусь, что это немного неясно, но это сводится к тому, что "ваша работа Read() тега конечного элемента оболочки".

Ответ 2

Я написал одну статью по этому вопросу с образцами, поскольку документация MSDN до сих пор довольно неясна, а примеры, которые вы можете найти в Интернете, в большинстве случаев неправильно реализованы.

Ловушки - это обработка локалей и пустых элементов рядом с тем, что уже упоминал Марк Гравелл.

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

Ответ 3

Да, все это немного минное поле, не так ли? Ответ Marc Gravell в значительной степени охватывает его, но я хотел бы добавить, что в проекте, над которым я работал, нам было довольно неудобно вручную писать внешний XML-элемент. Это также привело к непоследовательным именам XML-элементов для объектов того же типа.

Наше решение состояло в том, чтобы определить наш собственный интерфейс IXmlSerializable, полученный из системного, который добавил метод под названием WriteOuterXml(). Как вы можете догадаться, этот метод просто напишет внешний элемент, затем вызовет WriteXml(), а затем напишет конец элемента. Конечно, системный XML-сериализатор не будет вызывать этот метод, поэтому он был полезен только тогда, когда мы сделали свою собственную сериализацию, так что это может быть или не быть полезным в вашем случае. Аналогично, мы добавили метод ReadContentXml(), который не читал внешний элемент, а только его содержимое.

Ответ 4

Если у вас уже есть представление 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);
    }
}