Правильный способ реализации IXmlSerializable?

153

Как только программист решит реализовать 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>
Greg
источник
3
Не могли бы вы добавить образец XML к этому вопросу? Было бы проще читать вместе с кодом. Спасибо!
Рори
Как насчет случая, когда есть комментарий XML и т. Д. После последнего события в вашем xml. т.е. вы должны завершить метод ReadXml () чем-то, что проверяет, что вы прочитали до конечного элемента? В настоящее время предполагается, что это делает последняя функция Read (), но это может происходить не всегда.
Рори
7
@Rory - Образец добавлен. Лучше поздно, чем никогда?
Грег
@ Грег Хорошая информация. Не хотите ли вы, чтобы в ReadXml и WriteXml использовалась инвариантная культура? Я думаю, что вы можете столкнуться с проблемами, если пользователь переехал в другую страну и изменил свои настройки языка и региона. В этом случае код может неправильно десериализоваться. Я читал, что всегда рекомендуется использовать культуру инвариантов при сериализации
общедоступная беспроводная связь

Ответы:

100

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

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

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

Для записи :

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

И для чтения :

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

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

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

Марк Гравелл
источник
Как насчет записи и чтения элементов Event? Чувствовать себя плохо, когда вы пишете начальный элемент вручную. Я думаю, что я видел, как кто-то использовал XmlSerializer в методе write для записи каждого дочернего элемента.
Грег
@Greg; любое использование в порядке ... да, вы можете использовать вложенный XmlSerializer, если вам нужно, но это не единственный вариант.
Марк Гравелл
3
Спасибо за эту точность, пример кода внутри MSDN довольно бесполезен и неясен по этому поводу. Я застрял много раз и задавался вопросом об асимметричном поведении Read / WriteXml.
jdehaan
1
@MarcGravell Я знаю, что это старая тема. «Фреймворк записывает элемент-оболочку и позиционирует XML-модуль записи после его запуска». Вот где я борюсь. Есть ли способ заставить фреймворк пропустить этот шаг автоматической обработки оболочки? У меня есть ситуация, когда мне нужно пропустить этот шаг: stackoverflow.com/questions/20885455/…
Джеймс
@ Джеймс не в меру моих знаний
Марк Гравелл
34

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

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

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

jdehaan
источник
Отличная статья! Я определенно буду ссылаться на это в следующий раз, когда я собираюсь сериализовать некоторые данные.
Грег
Спасибо! количество положительных отзывов вознаграждает количество времени, потраченное на его написание. Я глубоко ценю, что вам это нравится! Не стесняйтесь просить критиковать некоторые моменты.
jdehaan
Примеры гораздо более полезны, чем цитирование MSDN.
Спасибо за проект кода, я бы тоже проголосовал, если бы мог. Материал по атрибутам был совершенно всеобъемлющим по сравнению с MSDN. Например, мой класс: IXMLSerializable прервался, когда в качестве префикса использовался сгенерированный xsd.exe [Serializable (), XmlType (Namespace = "MonitorService")].
Джон
8

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

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

EMP
источник
5
С C # 3.0 вы, вероятно, можете сделать это, написав вместо этого метод расширения, но интересная идея.
Марк Гравелл
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);
    }
}
Тийс Далхуйсен
источник
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)
    {
    }

}
VoteCoffee
источник