Вопрос

В продолжение моего предыдущий вопрос Я работал над сериализацией моей объектной модели в XML.Но теперь я столкнулся с проблемой (какой сюрприз!).

Проблема в том, что у меня есть коллекция абстрактного базового класса, заполненная конкретными производными типами.

Я подумал, что было бы неплохо просто добавить атрибуты XML ко всем задействованным классам, и все было бы отлично.К сожалению, это не так!

Итак, я немного покопался в Google и теперь понимаю почему это не работает.В этом тот XmlSerializer на самом деле выполняет какое-то умное размышление, чтобы сериализовать объекты в/из XML, и, поскольку оно основано на абстрактном типе, оно не может понять, с чем, черт возьми, оно разговаривает.Отлично.

я наткнулся эта страница на CodeProject, который, похоже, может очень помочь (пока не прочитан/использован полностью), но я подумал, что хотел бы перенести эту проблему и в таблицу StackOverflow, чтобы посмотреть, есть ли у вас какие-нибудь изящные приемы/хитрости, чтобы запустите это и запустите самым быстрым/легким способом.

Еще я должен добавить, что я НЕ хочу спуститься XmlInclude маршрут.С ним просто слишком много связей, и эта область системы находится в стадии активной разработки, так что это будет настоящая головная боль при обслуживании!

Это было полезно?

Решение

Задача решена!

Итак, я наконец добрался туда (правда, с много помощи от здесь!).

Итак, подведем итоги:

Цели:

  • Я не хотел спускаться по XmlInclude маршрут из-за головной боли при обслуживании.
  • Как только решение было найдено, я хотел, чтобы его можно было быстро реализовать в других приложениях.
  • Можно использовать коллекции абстрактных типов, а также отдельные абстрактные свойства.
  • На самом деле мне не хотелось беспокоиться о том, чтобы делать «особые» вещи на конкретных занятиях.

Выявленные проблемы/на что следует обратить внимание:

  • XmlSerializer делает довольно крутое отражение, но это очень ограничено, когда дело доходит до абстрактных типов (т.е.он будет работать только с экземплярами самого абстрактного типа, а не с подклассами).
  • Декораторы атрибутов Xml определяют, как XmlSerializer обрабатывает найденные свойства.Физический тип также может быть указан, но это создает тесная связь между классом и сериализатором (нехорошо).
  • Мы можем реализовать наш собственный XmlSerializer, создав класс, реализующий IXmlSerializable .

Решение

Я создал универсальный класс, в котором вы указываете универсальный тип как абстрактный тип, с которым вы будете работать.Это дает классу возможность «переводить» между абстрактным типом и конкретным типом, поскольку мы можем жестко запрограммировать приведение (т.мы можем получить больше информации, чем XmlSerializer).

Затем я реализовал IXmlSerializable интерфейса, это довольно просто, но при сериализации нам необходимо убедиться, что мы записываем тип конкретного класса в XML, чтобы мы могли вернуть его при десериализации.Также важно отметить, что это должно быть Полностью квалифицированный поскольку сборки, в которых находятся эти два класса, скорее всего, будут различаться.Конечно, здесь необходимо выполнить небольшую проверку типов и тому подобное.

Поскольку XmlSerializer не может выполнять приведение, нам нужно предоставить код для этого, поэтому неявный оператор затем перегружается (я даже не знал, что вы можете это сделать!).

Код для AbstractXmlSerializer следующий:

using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Serialization;

namespace Utility.Xml
{
    public class AbstractXmlSerializer<AbstractType> : IXmlSerializable
    {
        // Override the Implicit Conversions Since the XmlSerializer
        // Casts to/from the required types implicitly.
        public static implicit operator AbstractType(AbstractXmlSerializer<AbstractType> o)
        {
            return o.Data;
        }

        public static implicit operator AbstractXmlSerializer<AbstractType>(AbstractType o)
        {
            return o == null ? null : new AbstractXmlSerializer<AbstractType>(o);
        }

        private AbstractType _data;
        /// <summary>
        /// [Concrete] Data to be stored/is stored as XML.
        /// </summary>
        public AbstractType Data
        {
            get { return _data; }
            set { _data = value; }
        }

        /// <summary>
        /// **DO NOT USE** This is only added to enable XML Serialization.
        /// </summary>
        /// <remarks>DO NOT USE THIS CONSTRUCTOR</remarks>
        public AbstractXmlSerializer()
        {
            // Default Ctor (Required for Xml Serialization - DO NOT USE)
        }

        /// <summary>
        /// Initialises the Serializer to work with the given data.
        /// </summary>
        /// <param name="data">Concrete Object of the AbstractType Specified.</param>
        public AbstractXmlSerializer(AbstractType data)
        {
            _data = data;
        }

        #region IXmlSerializable Members

        public System.Xml.Schema.XmlSchema GetSchema()
        {
            return null; // this is fine as schema is unknown.
        }

        public void ReadXml(System.Xml.XmlReader reader)
        {
            // Cast the Data back from the Abstract Type.
            string typeAttrib = reader.GetAttribute("type");

            // Ensure the Type was Specified
            if (typeAttrib == null)
                throw new ArgumentNullException("Unable to Read Xml Data for Abstract Type '" + typeof(AbstractType).Name +
                    "' because no 'type' attribute was specified in the XML.");

            Type type = Type.GetType(typeAttrib);

            // Check the Type is Found.
            if (type == null)
                throw new InvalidCastException("Unable to Read Xml Data for Abstract Type '" + typeof(AbstractType).Name +
                    "' because the type specified in the XML was not found.");

            // Check the Type is a Subclass of the AbstractType.
            if (!type.IsSubclassOf(typeof(AbstractType)))
                throw new InvalidCastException("Unable to Read Xml Data for Abstract Type '" + typeof(AbstractType).Name +
                    "' because the Type specified in the XML differs ('" + type.Name + "').");

            // Read the Data, Deserializing based on the (now known) concrete type.
            reader.ReadStartElement();
            this.Data = (AbstractType)new
                XmlSerializer(type).Deserialize(reader);
            reader.ReadEndElement();
        }

        public void WriteXml(System.Xml.XmlWriter writer)
        {
            // Write the Type Name to the XML Element as an Attrib and Serialize
            Type type = _data.GetType();

            // BugFix: Assembly must be FQN since Types can/are external to current.
            writer.WriteAttributeString("type", type.AssemblyQualifiedName);
            new XmlSerializer(type).Serialize(writer, _data);
        }

        #endregion
    }
}

Итак, как нам сказать XmlSerializer, чтобы он работал с нашим сериализатором, а не с сериализатором по умолчанию?Мы должны передать наш тип в свойстве типа атрибутов Xml, например:

[XmlRoot("ClassWithAbstractCollection")]
public class ClassWithAbstractCollection
{
    private List<AbstractType> _list;
    [XmlArray("ListItems")]
    [XmlArrayItem("ListItem", Type = typeof(AbstractXmlSerializer<AbstractType>))]
    public List<AbstractType> List
    {
        get { return _list; }
        set { _list = value; }
    }

    private AbstractType _prop;
    [XmlElement("MyProperty", Type=typeof(AbstractXmlSerializer<AbstractType>))]
    public AbstractType MyProperty
    {
        get { return _prop; }
        set { _prop = value; }
    }

    public ClassWithAbstractCollection()
    {
        _list = new List<AbstractType>();
    }
}

Здесь вы можете видеть, что у нас есть коллекция и одно свойство, и все, что нам нужно сделать, это добавить тип именованный параметр в декларацию Xml, легко!:D

ПРИМЕЧАНИЕ:Если вы используете этот код, я буду очень признателен за похвалу.Это также поможет привлечь больше людей в сообщество :)

Теперь, но я не уверен, что делать с ответами здесь, поскольку у всех были свои плюсы и минусы.Я обновлю те, которые, по моему мнению, были полезны (не в обиду тем, кто не был полезен), и закрою это, как только у меня будет представитель :)

Интересная задача и приятное решение!:)

Другие советы

Следует обратить внимание на тот факт, что в конструкторе XmlSerialiser вы можете передать массив типов, с разрешением которых у сериализатора могут возникнуть трудности.Мне приходилось использовать это довольно много раз, когда нужно было сериализовать коллекцию или сложный набор структур данных, и эти типы находились в разных сборках и т. д.

Конструктор XmlSerialiser с параметром extraTypes

РЕДАКТИРОВАТЬ:Я бы добавил, что этот подход имеет преимущество перед атрибутами XmlInclude и т. д.: вы можете найти и составить список возможных конкретных типов во время выполнения и вставить их.

Серьезно, расширяемая структура POCO никогда не будет надежно сериализоваться в XML.Я говорю это потому, что могу гарантировать, что кто-нибудь придет, расширит ваш класс и все испортит.

Вам следует изучить возможность использования XAML для сериализации графов объектов.Он предназначен для этого, тогда как сериализация XML — нет.

Сериализатор и десериализатор Xaml без проблем обрабатывают универсальные шаблоны, а также коллекции базовых классов и интерфейсов (при условии, что сами коллекции реализуют IList или IDictionary).Есть некоторые предостережения, например, помечайте свойства коллекции только для чтения с помощью значка DesignerSerializationAttribute, но переработать код для обработки таких крайних случаев не так уж и сложно.

Просто быстрое обновление по этому поводу, я не забыл!

Просто провожу еще несколько исследований, похоже, я нахожусь в выигрыше, просто нужно отсортировать код.

На данный момент у меня есть следующее:

  • А XmlСерализатор по сути, это класс, который изящно отражает сериализуемые классы.Он определяет свойства, которые сериализуются на основе Тип.
  • Причина возникновения проблемы заключается в том, что происходит несоответствие типов, ожидается Базовый тип но на самом деле получает Дериведтип ..Хотя вы можете подумать, что он будет относиться к нему полиморфно, это не так, поскольку это потребует целой дополнительной нагрузки по отражению и проверке типов, для которой он не предназначен.

Похоже, что это поведение можно переопределить (ожидание кода) путем создания прокси-класса, который будет выступать в качестве посредника для сериализатора.По сути, это определит тип производного класса, а затем сериализует его как обычно.Затем этот прокси-класс передаст этот XML в резервную копию основного сериализатора.

Следите за этим пространством!^_^

Это, конечно, решение вашей проблемы, но есть еще одна проблема, которая несколько подрывает ваше намерение использовать «переносимый» формат XML.Плохая вещь случается, когда вы решаете изменить классы в следующей версии вашей программы и вам нужно поддерживать оба формата сериализации — новый и старый (потому что ваши клиенты все еще используют свои старые файлы/базы данных или подключаются к ваш сервер использует старую версию вашего продукта).Но вы больше не можете использовать этот сериализатор, потому что использовали

type.AssemblyQualifiedName

который выглядит как

TopNamespace.SubNameSpace.ContainingClass+NestedClass, MyAssembly, Version=1.3.0.0, Culture=neutral, PublicKeyToken=b17a5c561934e089

который содержит атрибуты и версию сборки...

Теперь, если вы попытаетесь изменить версию сборки или решите подписать ее, эта десериализация не сработает...

Я делал подобные вещи.Обычно я проверяю, что все атрибуты сериализации XML находятся в конкретном классе, и просто передаю свойства этого класса через базовые классы (где это необходимо) для получения информации, которая будет де/сериализована при вызове сериализатора. эти свойства.Это немного больше работы по кодированию, но это работает намного лучше, чем попытка заставить сериализатор просто делать правильные вещи.

Еще лучше, используя обозначения:

[XmlRoot]
public class MyClass {
    public abstract class MyAbstract {} 
    public class MyInherited : MyAbstract {} 
    [XmlArray(), XmlArrayItem(typeof(MyInherited))] 
    public MyAbstract[] Items {get; set; } 
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top