Правильный способ реализации IXmlSerializable? - PullRequest
143 голосов
/ 11 ноября 2008

Как только программист решит внедрить 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>

Ответы [ 4 ]

92 голосов
/ 11 ноября 2008

Да, GetSchema () должен возвращать ноль .

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

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

Для записать :

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

А для читать :

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

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

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

32 голосов
/ 27 октября 2009

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

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

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

8 голосов
/ 19 апреля 2010

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

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

2 голосов
/ 02 апреля 2014

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