Problema resolvido!
OK, então finalmente cheguei lá (admito que com muita ajuda daqui !).
Portanto, resuma:
Metas:
- Eu não queria seguir a rota XmlInclude devido à dor de cabeça de manutenção.
- Assim que uma solução fosse encontrada, eu queria que ela fosse rápida para implementar em outros aplicativos.
- Podem ser usadas coleções de tipos abstratos, bem como propriedades abstratas individuais.
- Eu realmente não queria me preocupar em ter que fazer coisas "especiais" nas aulas de concreto.
Problemas identificados / pontos a serem observados:
- XmlSerializer faz uma reflexão muito legal, mas é muito limitado quando se trata de tipos abstratos (ou seja, ele só funcionará com instâncias do próprio tipo abstrato, não subclasses).
- Os decoradores de atributo Xml definem como o XmlSerializer trata as propriedades que encontra. O tipo físico também pode ser especificado, mas isso cria um acoplamento estreito entre a classe e o serializador (não é bom).
- Podemos implementar nosso próprio XmlSerializer criando uma classe que implementa IXmlSerializable .
A solução
Eu criei uma classe genérica, na qual você especifica o tipo genérico como o tipo abstrato com o qual trabalhará. Isso dá à classe a capacidade de "traduzir" entre o tipo abstrato e o tipo concreto, uma vez que podemos codificar o casting (ou seja, podemos obter mais informações do que o XmlSerializer pode).
Em seguida, implementei a interface IXmlSerializable , isso é bastante simples, mas ao serializar, precisamos garantir que escreveremos o tipo da classe concreta no XML, para que possamos lançá-lo de volta ao desserializar. Também é importante observar que ele deve ser totalmente qualificado, pois os conjuntos em que as duas classes estão provavelmente serão diferentes. É claro que há uma pequena verificação de tipo e outras coisas que precisam acontecer aqui.
Como o XmlSerializer não pode converter, precisamos fornecer o código para fazer isso, de forma que o operador implícito fique sobrecarregado (eu nem sabia que você poderia fazer isso!).
O código para AbstractXmlSerializer é 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
}
}
Então, a partir daí, como podemos dizer ao XmlSerializer para trabalhar com nosso serializador em vez do padrão? Devemos passar nosso tipo dentro da propriedade de tipo de atributos Xml, por exemplo:
[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>();
}
}
Aqui você pode ver, temos uma coleção e uma única propriedade sendo exposta, e tudo o que precisamos fazer é adicionar o parâmetro nomeado de tipo à declaração Xml, fácil! : D
NOTA: Se você usar este código, eu realmente agradeceria uma mensagem. Também ajudará a atrair mais pessoas para a comunidade :)
Agora, mas sem saber o que fazer com as respostas aqui, já que todos eles tinham seus prós e contras. Vou atualizar aqueles que considero úteis (sem ofender os que não foram) e encerrar assim que tiver a representação :)
Problema interessante e divertido de resolver! :)