Сведения о вопросе

krutoi

16:53, 23rd August, 2020

Теги

XML Serialization и унаследованные типы

Просмотров: 519   Ответов: 7

Следуя из моего предыдущего вопроса , я работал над тем, чтобы моя объектная модель сериализовалась до XML. Но теперь я столкнулся с проблемой (quelle surprise!).

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

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

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

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

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



  Сведения об ответе

PROGA

02:04, 17th August, 2020

Проблема Решена!

OK, так что я, наконец, добрался туда (по общему признанию, с большой помощью отсюда !).

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

Цели:

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

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

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

решение

Я создал универсальный класс, в котором вы указываете универсальный тип как абстрактный тип, с которым вы будете работать. Это дает классу возможность "translate" между абстрактным типом и конкретным типом, так как мы можем жестко кодировать литье (т. е. мы можем получить больше информации, чем 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 attributes type, например:

[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, легко! :Д

NOTE: если вы используете этот код,я был бы очень признателен за крик. Это также поможет привлечь больше людей в сообщество :)

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

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


  Сведения об ответе

JUST___

08:18, 3rd August, 2020

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

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

EDIT: я бы добавил, что этот подход имеет преимущество перед XmlInclude атрибутами и т. д., Что вы можете разработать способ обнаружения и составления списка ваших возможных конкретных типов во время выполнения и наполнить их.


  Сведения об ответе

ASER

02:27, 7th August, 2020

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

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

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


  Сведения об ответе

VCe znayu

15:29, 16th August, 2020

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

Просто делаю еще одно исследование, похоже, я нахожусь на победителе,просто нужно разобраться с кодом.

Пока что у меня есть следующее:

  • XmlSeralizer -это в основном класс, который делает некоторое изящное отражение на классах, которые он сериализует. Он определяет свойства, которые сериализуются на основе типа .
  • Причина возникновения проблемы заключается в том, что происходит несоответствие типа, он ожидает BaseType , но на самом деле получает DerivedType .. Хотя вы можете подумать, что он будет относиться к нему полиморфно, это не так, поскольку он будет включать в себя целую дополнительную нагрузку по отражению и проверке типов, для чего он не предназначен.

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

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


  Сведения об ответе

SKY

22:15, 15th August, 2020

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

type.AssemblyQualifiedName

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

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

то есть содержит ваши assembly атрибуты и версию...

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


  Сведения об ответе

SILA

22:05, 10th August, 2020

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


  Сведения об ответе

lool

13:21, 9th August, 2020

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

[XmlRoot]
public class MyClass {
    public abstract class MyAbstract {} 
    public class MyInherited : MyAbstract {} 
    [XmlArray(), XmlArrayItem(typeof(MyInherited))] 
    public MyAbstract[] Items {get; set; } 
}


Ответить на вопрос

Чтобы ответить на вопрос вам нужно войти в систему или зарегистрироваться