Задача решена!
Хорошо, вот я наконец добрался до цели (правда, с большой помощью отсюда !).
Итак, резюмируйте:
Цели:
- Я не хотел идти по маршруту 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
{
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;
public AbstractType Data
{
get { return _data; }
set { _data = value; }
}
public AbstractXmlSerializer()
{
}
public AbstractXmlSerializer(AbstractType data)
{
_data = data;
}
#region IXmlSerializable Members
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
string typeAttrib = reader.GetAttribute("type");
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);
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.");
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 + "').");
reader.ReadStartElement();
this.Data = (AbstractType)new
XmlSerializer(type).Deserialize(reader);
reader.ReadEndElement();
}
public void WriteXml(System.Xml.XmlWriter writer)
{
Type type = _data.GetType();
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
ПРИМЕЧАНИЕ. Если вы используете этот код, я буду очень признателен. Это также поможет привлечь больше людей в сообщество :)
Теперь, но не уверен, что здесь делать с ответами, поскольку у всех были свои за и против. Я улучшу те, которые, на мой взгляд, были полезны (без обид на тех, кто не был), и закрою это, когда у меня будет репутация :)
Интересная задача и весело ее решить! :)