Следуя моему предыдущему вопросу, я работал над тем, чтобы моя объектная модель была сериализована в XML. Но теперь я столкнулся с проблемой (сюрприз quelle!).
У меня проблема в том, что у меня есть коллекция абстрактного базового класса, которая заполняется конкретными производными типами.
Я подумал, что было бы хорошо просто добавить атрибуты XML ко всем задействованным классам, и все было бы замечательно. К сожалению, это не так!
Итак, я покопался в Google и теперь понимаю, почему он не работает. В том , что фактически делает некоторые умные размышления, чтобы сериализации объектов в / из XML, и с момента его на основе абстрактного типа, он не может понять, что, черт возьми , он разговаривает . Хорошо.XmlSerializer
Я наткнулся на эту страницу в CodeProject, которая, похоже, может очень помочь (еще не прочитана / потреблена полностью), но я подумал, что хотел бы перенести эту проблему и в таблицу StackOverflow, чтобы посмотреть, есть ли у вас аккуратные хаки / уловки, чтобы запустить его как можно быстрее и легче.
Еще я должен добавить, что я НЕ хочу идти по XmlInclude
маршруту. С ним просто слишком много связи, и эта область системы находится в стадии интенсивной разработки, так что это была бы настоящая головная боль при обслуживании!
источник
Ответы:
Задача решена!
Хорошо, вот я наконец добрался до цели (правда, с большой помощью отсюда !).
Итак, резюмируйте:
Цели:
Выявленные проблемы / моменты, на которые следует обратить внимание:
Решение
Я создал универсальный класс, в котором вы указываете общий тип как абстрактный тип, с которым вы будете работать. Это дает классу возможность «переводить» между абстрактным типом и конкретным типом, поскольку мы можем жестко запрограммировать приведение (т.е. мы можем получить больше информации, чем может 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
ПРИМЕЧАНИЕ. Если вы используете этот код, я буду очень признателен. Это также поможет привлечь больше людей в сообщество :)
Теперь, но не уверен, что здесь делать с ответами, поскольку у всех были свои за и против. Я улучшу те, которые, на мой взгляд, были полезны (без обид на тех, кто не был), и закрою это, когда у меня будет репутация :)
Интересная задача и весело ее решить! :)
источник
private
илиprotected
заставить его быть недоступным для других классов.Следует обратить внимание на тот факт, что в конструкторе XmlSerialiser вы можете передать массив типов, которые сериализатор может испытывать трудности с разрешением. Мне приходилось использовать это довольно много раз, когда коллекцию или сложный набор структур данных нужно было сериализовать, и эти типы жили в разных сборках и т. Д.
Конструктор XmlSerialiser с параметром extraTypes
РЕДАКТИРОВАТЬ: Я бы добавил, что этот подход имеет преимущество перед атрибутами XmlInclude и т. Д., Что вы можете разработать способ обнаружения и компиляции списка ваших возможных конкретных типов во время выполнения и их заполнения.
источник
Серьезно, расширяемая структура POCO никогда не будет надежно сериализована в XML. Я говорю это, потому что могу гарантировать, что кто-то придет, расширит ваш класс и все испортит.
Вам следует изучить использование XAML для сериализации графов объектов. Он предназначен для этого, тогда как сериализация XML - нет.
Сериализатор и десериализатор Xaml без проблем обрабатывает универсальные шаблоны, а также коллекции базовых классов и интерфейсов (если сами коллекции реализуют
IList
илиIDictionary
). Есть некоторые предостережения, например, пометка свойств коллекции только для чтения с помощьюDesignerSerializationAttribute
, но переработать код для обработки этих угловых случаев не так уж и сложно.источник
Просто краткое обновление по этому поводу, я не забыл!
Просто провожу дополнительное исследование, похоже, я нахожусь в выигрыше, просто нужно разобрать код.
Пока у меня есть следующее:
Это поведение, похоже, можно переопределить (ожидающий код), создав прокси-класс, который будет действовать как посредник для сериализатора. Это в основном определит тип производного класса, а затем сериализует его как обычно. Затем этот прокси-класс будет передавать этот 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; } }
источник