Проблема решена!
ОК, так что я наконец-то пришел (правда, с лотом помощи от здесь !).
Итак, подведем итог:
Цели:
- Я не хотел идти по маршруту 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>();
}
}
Здесь вы видите, что у нас есть коллекция и одно свойство, и все, что нам нужно сделать, это добавить именованный параметр type в объявление Xml, просто! : D
ПРИМЕЧАНИЕ. Если вы воспользуетесь этим кодом, я был бы очень признателен. Это также поможет привлечь больше людей к сообществу:)
Теперь, но не уверен, что делать с ответами здесь, так как у них всех были свои плюсы и минусы. Я добавлю те, которые, по моему мнению, были полезны (не оскорбляю тех, кто не был), и закрою это, как только получу повтор:)
Интересная проблема и хорошее развлечение! :)