Сериализация XML и наследуемые типы
исходя из моего предыдущего вопроса я работаю на получение моей модели объекта для сериализации в XML. Но теперь я столкнулся с проблемой (quelle surprise!).
проблема у меня есть то, что у меня есть коллекция, которая имеет абстрактный тип базового класса, который заполняется конкретными производными типами.
Я подумал, что было бы неплохо просто добавить атрибуты XML ко всем задействованным классам, и все будет персиковым. К сожалению, это не случае!
Итак, я немного покопался в Google, и теперь я понимаю почему это не работает. В этом the XmlSerializer на самом деле делает некоторые умные размышления, чтобы сериализовать объекты в/из XML, и поскольку он основан на абстрактном типе, он не может понять, что, черт возьми, он говорит с. Штраф.
Я пришел через на этой странице на CodeProject, который выглядит так, что вполне может помочь много (пока не читать / потреблять полностью), но я думал, что я хотел бы принести эту проблему в таблицу StackOverflow тоже, чтобы увидеть, есть ли у вас какие-либо аккуратные хаки/трюки, чтобы получить это и работать самым быстрым/легким способом.
одна вещь, которую я должен также добавить, что я НЕ хочу спуститься XmlInclude маршрут. Просто слишком много сцепления с ним, и эта область системы находится в тяжелом развитии, поэтому это будет настоящая головная боль обслуживания!
7 ответов:
Проблема Решена!
хорошо, так что я, наконец, добрался туда (по общему признанию, с много С здесь!).
Итак, подведем итог:
цели:
- я не хотел спускаться 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
Примечание: Если вы используете этот код, я был бы очень признателен кричать. Это также поможет привлечь больше людей в сообщество :)
теперь, но не уверен, что делать с ответами здесь, так как у всех были свои плюсы и минусы. я буду upmod те, которые я чувствую, были полезны (без обид на тех, кто не был) и закрыть это, как только у меня есть репутация:)
интересные задачи и хорошее удовольствие, чтобы решить! :)
следует обратить внимание на тот факт, что в конструкторе XmlSerialiser вы можете передать массив типов, которые сериализатор может иметь трудности с разрешением. Мне приходилось использовать это довольно много раз, когда нужно было сериализовать коллекцию или сложный набор структур данных, и эти типы жили в разных сборках и т. д.
конструктор XmlSerialiser с extraTypes парам
EDIT: я бы добавил, что этот подход имеет преимущество над Xmlinclude атрибуты и т. д., которые вы можете разработать способ обнаружения и компиляции списка ваших возможных конкретных типов во время выполнения и наполнить их.
серьезно, расширяемая структура POCOs никогда не будет сериализоваться в XML надежно. Я говорю это, потому что могу гарантировать, что кто-то придет, расширит ваш класс и испортит его.
вы должны изучить использование XAML для сериализации графов объектов. Он предназначен для этого, в то время как XML-сериализации нет.
сериализатор и десериализатор Xaml без проблем обрабатывает универсальные шаблоны, коллекции базовых классов и интерфейсы (до тех пор, пока коллекции сами реализуют
IListилиIDictionary). Есть некоторые предостережения, такие как маркировка свойств коллекции только для чтения с помощьюDesignerSerializationAttribute, но переработать код для обработки этих угловых случаев не так уж сложно.
просто быстрое обновление, я не забыл!
просто делаю еще несколько исследований, похоже, я нахожусь на победителя, просто нужно отсортировать код.
пока у меня есть следующие:
- The XmlSeralizer в основном это класс, который делает некоторые изящные отражения на классах, которые он сериализует. Он определяет свойства, которые сериализуются на основе тип.
- причина проблемы происходит это потому, что происходит несоответствие типа, он ожидает BaseType но на самом деле получает DerivedType .. Хотя вы можете подумать, что он будет рассматривать его полиморфно, это не так, поскольку он будет включать в себя целую дополнительную нагрузку на отражение и проверку типов, для чего он не предназначен.
Это поведение, по-видимому, может быть переопределено (ожидание кода) путем создания прокси-класса, который будет выступать в качестве посредника для сериализатора. Этот в основном будет определять тип производного класса, а затем сериализовать его как обычно. Затем этот прокси-класс будет передавать этот 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; } }
Comments