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

3
Чи можете ви додати зразок xml до цього питання? Було б простіше читати разом з кодом. Дякую!
Рорі

Як щодо розгляду випадку, коли після останньої події у вашому xml є коментар XML тощо. тобто ви повинні закінчити метод ReadXml () чимось, що перевіряє, що ви прочитали, до кінцевого елемента? В даний час це передбачає, що останній читання () робить це, але може не завжди.
Rory

7
@Rory - зразок доданий. Краще пізно, ніж ніколи?
Грег

@Greg Хороша інформація. Ви також не хочете, щоб ReadXml і WriteXml використовували Інваріантну культуру? Я думаю, у вас можуть виникнути проблеми, якщо користувач переїхав до іншої країни та змінив налаштування регіону та мови. У цьому випадку код може бути неправильно дезаріалізований. Я читав, що найкраща практика завжди використовувати Інваріантну культуру при серіалізації
громадський бездротовий доступ

Відповіді:


100

Так, GetSchema () повинен повернути нуль .

Метод IXmlSerializable.GetSchema Цей метод зарезервований, і його не слід використовувати. Реалізуючи інтерфейс IXmlSerializable, ви повинні повернути нульову посилання (Нічого в Visual Basic) з цього методу, і замість цього, якщо потрібна спеціальна схема, застосуйте XmlSchemaProviderAttribute до класу.

Як для читання, так і для запису об'єктний елемент уже записаний, тому вам не потрібно додавати зовнішній елемент у записі. Наприклад, ви можете просто почати читання / запис атрибутів у двох.

Для написання :

Введена вами реалізація WriteXml повинна виписати XML-представлення об'єкта. Рамка пише елемент обгортки та розміщує запису XML після його запуску. Ваша реалізація може записати її вміст, включаючи дочірні елементи. Потім рамка закриває елемент обгортки.

І для читання :

Метод ReadXml повинен відновити об'єкт, використовуючи інформацію, написану методом WriteXml.

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

Я погоджуся, що це трохи незрозуміло, але це зводиться до "це ваша робота до Read()тегу кінцевих елементів обгортки".


Що з написанням та читанням елементів події? Руйнувати початковий елемент вручну. Я думаю, я бачив, як хтось використовує XmlSerializer у методі записи для написання кожного дочірнього елемента.
Грег

@Greg; або використання нормально ... так, ви можете використовувати вкладений XmlSerializer, якщо вам потрібно, але це не єдиний варіант.
Марк Гравелл

3
Дякуючи за ці вказівки, зразок коду в MSDN є досить невикорисливим і незрозумілим щодо цього. Я багато разів зациклювався і цікавився асиметричною поведінкою Read / WriteXml.
jdehaan

1
@MarcGravell Я знаю, що це стара тема. "Рамка пише елемент обгортки і розміщує запису XML після його запуску." Тут я борюся. Чи є спосіб змусити рамку пропустити цей крок автоматичної обробки обгортки? У мене є ситуація, коли мені потрібно пропустити цей крок: stackoverflow.com/questions/20885455/…
Джеймс

@James не наскільки мені відомо
Марк Гравелл

34

Я написав одну статтю з цього приводу із зразками, оскільки документація MSDN на сьогоднішній день є доволі незрозумілою, і приклади, які ви можете знайти в Інтернеті, більшість часу неправильно реалізовані.

Підводні камені - це обробка локалів та порожніх елементів поруч із тим, що вже згадував Марк Гравелл.

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


Відмінна стаття! Я обов'язково посилаюся на це наступного разу, коли я прагну серіалізувати деякі дані.
Грег

Дякую! кількість позитивних відгуків винагороджує кількість часу, вкладеного в його написання. Я глибоко вдячний, що вам це подобається! Не соромтеся просити критикувати деякі моменти.
jdehaan

Приклади набагато корисніші, ніж цитування MSDN.

Дякую за кодовий проект, я б і проголосував за це, якби міг. Дані про атрибути були надзвичайно вичерпними порівняно з MSDN. Наприклад, мій: IXMLSerializable клас зламався, коли префікс створено xsd.exe, згенерованого [Serializable (), XmlType (Namespace = "MonitorService")].
Іван,

8

Так, вся справа трохи мінне поле, чи не так? Відповідь Марка Гравлла в значній мірі охоплює це, але я хотів би додати, що в проекті, над яким я працював, нам було незручно вручну писати зовнішній XML-елемент. Це також призвело до непослідовних назв елементів XML для об'єктів одного типу.

Нашим рішенням було визначити власний IXmlSerializableінтерфейс, похідний від системного, який додав метод, який називається WriteOuterXml(). Як ви здогадаєтесь, цей метод просто запише зовнішній елемент, потім дзвонить WriteXml(), а потім пише кінець елемента. Звичайно, системний серіалізатор XML не називав би цей метод, тому він був корисним лише тоді, коли ми зробили власну серіалізацію, так що може бути, а може і не бути корисною у вашому випадку. Так само ми додали ReadContentXml()метод, який не читав зовнішній елемент, а лише його вміст.


5
З C # 3.0 ви, ймовірно, можете це зробити, написавши натомість метод розширення, але цікаву ідею.
Марк Гравелл

2

Якщо у вас вже є представлення XmlDocument вашого класу або ви віддаєте перевагу XmlDocument спосіб роботи зі структурами XML, швидкий і брудний спосіб реалізації IXmlSerializable - це просто передати цей xmldoc різним функціям.

ПОПЕРЕДЖЕННЯ: XmlDocument (та / або XDocument) - це на порядок повільніше, ніж xmlreader / write, тому якщо продуктивність є абсолютною вимогою, це рішення не для вас!

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)
    {
    }

}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.