¡Problema resuelto!
Bien, finalmente llegué allí (¡es cierto que con mucha ayuda de aquí !).
Así que resuma:
Metas:
- No quería seguir la ruta XmlInclude debido al dolor de cabeza de mantenimiento.
- Una vez que se encontró una solución, quería que se implementara rápidamente en otras aplicaciones.
- Se pueden utilizar colecciones de tipos abstractos, así como propiedades abstractas individuales.
- Realmente no quería molestarme en tener que hacer cosas "especiales" en las clases concretas.
Problemas identificados / puntos a tener en cuenta:
- XmlSerializer hace una reflexión bastante interesante, pero es muy limitado cuando se trata de tipos abstractos (es decir, solo funcionará con instancias del tipo abstracto en sí, no con subclases).
- Los decoradores de atributos Xml definen cómo XmlSerializer trata las propiedades que encuentra. El tipo físico también se puede especificar, pero esto crea un acoplamiento estrecho entre la clase y el serializador (no es bueno).
- Podemos implementar nuestro propio XmlSerializer creando una clase que implemente IXmlSerializable .
La solución
Creé una clase genérica, en la que especificas el tipo genérico como el tipo abstracto con el que trabajarás. Esto le da a la clase la capacidad de "traducir" entre el tipo abstracto y el tipo concreto, ya que podemos codificar la conversión (es decir, podemos obtener más información de la que puede obtener el XmlSerializer).
Luego implementé la interfaz IXmlSerializable , esto es bastante sencillo, pero al serializar debemos asegurarnos de escribir el tipo de la clase concreta en el XML, para que podamos devolverlo al deserializar. También es importante tener en cuenta que debe estar completamente calificado ya que es probable que los ensamblados en los que se encuentran las dos clases difieran. Por supuesto, hay una pequeña verificación de tipos y cosas que deben suceder aquí.
Dado que XmlSerializer no puede emitir, necesitamos proporcionar el código para hacer eso, por lo que el operador implícito se sobrecarga (¡ni siquiera sabía que podía hacer esto!).
El código para AbstractXmlSerializer es este:
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
}
}
Entonces, a partir de ahí, ¿cómo le decimos al XmlSerializer que funcione con nuestro serializador en lugar del predeterminado? Debemos pasar nuestro tipo dentro de la propiedad de tipo de atributos Xml, por ejemplo:
[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>();
}
}
Aquí puede ver, tenemos una colección y una sola propiedad expuesta, y todo lo que tenemos que hacer es agregar el parámetro con nombre de tipo a la declaración Xml, ¡fácil! :RE
NOTA: Si usa este código, le agradecería mucho un reconocimiento. También ayudará a atraer a más personas a la comunidad :)
Ahora, pero no estoy seguro de qué hacer con las respuestas aquí, ya que todos tenían sus pros y sus contras. Mejoraré los que considero útiles (sin ofender a los que no lo sean) y cerraré esto una vez que tenga la reputación :)
¡Problema interesante y divertido de resolver! :)