Continuando com minha pergunta anterior , tenho trabalhado para que meu modelo de objeto seja serializado em XML. Mas agora encontrei um problema (quelle surpresa!).
O problema que tenho é que tenho uma coleção, que é de um tipo de classe base abstrata, que é preenchida pelos tipos derivados concretos.
Achei que seria ótimo apenas adicionar os atributos XML a todas as classes envolvidas e tudo ficaria bem. Infelizmente, esse não é o caso!
Então, fiz algumas pesquisas no Google e agora entendo por que não está funcionando. Na verdade, o XmlSerializer
está fazendo uma reflexão inteligente para serializar objetos de / para XML e, como é baseado no tipo abstrato, não consegue descobrir com que diabos está falando . Bem.
Eu encontrei esta página no CodeProject, que parece que pode ajudar muito (ainda para ler / consumir totalmente), mas pensei que gostaria de trazer esse problema para a tabela StackOverflow também, para ver se você tem algum hacks / truques para colocá-lo em funcionamento da maneira mais rápida / leve possível.
Uma coisa que também devo acrescentar é que NÃO quero seguir o XmlInclude
caminho. Simplesmente há muito acoplamento com ele, e esta área do sistema está sob intenso desenvolvimento, então seria uma verdadeira dor de cabeça para manutenção!
fonte
Respostas:
Problema resolvido!
OK, então finalmente cheguei lá (admito que com muita ajuda daqui !).
Portanto, resuma:
Metas:
Problemas identificados / pontos a serem observados:
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 { // 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 } }
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! :)
fonte
private
ouprotected
fazer com que ele não esteja disponível para outras classes.Uma coisa a se observar é o fato de que no construtor XmlSerialiser você pode passar uma matriz de tipos que o serializador pode estar tendo dificuldade em resolver. Eu tive que usar isso algumas vezes, onde uma coleção ou conjunto complexo de estruturas de dados precisava ser serializado e esses tipos viviam em diferentes montagens, etc.
Construtor XmlSerialiser com parâmetro extraTypes
EDIT: Eu acrescentaria que esta abordagem tem o benefício sobre os atributos XmlInclude etc que você pode trabalhar em uma maneira de descobrir e compilar uma lista de seus possíveis tipos concretos em tempo de execução e colocá-los dentro.
fonte
Sério, uma estrutura extensível de POCOs nunca será serializada para XML de forma confiável. Digo isso porque posso garantir que alguém vai aparecer, estender sua aula e estragar tudo.
Você deve procurar usar XAML para serializar seus gráficos de objeto. Ele foi projetado para fazer isso, enquanto a serialização XML não.
O serializador e desserializador Xaml lida com genéricos sem problemas, coleções de classes base e interfaces também (contanto que as próprias coleções implementem
IList
ouIDictionary
). Existem algumas ressalvas, como marcar suas propriedades de coleção somente leitura com oDesignerSerializationAttribute
, mas retrabalhar seu código para lidar com esses casos secundários não é tão difícil.fonte
Apenas uma atualização rápida sobre isso, eu não esqueci!
Apenas fazendo mais pesquisas, parece que estou no caminho certo para um vencedor, só preciso classificar o código.
Até agora, tenho o seguinte:
Esse comportamento parece poder ser substituído (código pendente) criando uma classe de proxy para atuar como intermediária para o serializador. Basicamente, isso determinará o tipo da classe derivada e a serializará normalmente. Essa classe de proxy alimentará esse XML de backup da linha para o serializador principal.
Assista esse espaço! ^ _ ^
fonte
Certamente é uma solução para o seu problema, mas há outro problema, que de certa forma prejudica sua intenção de usar o formato XML "portátil". Coisas ruins acontecem quando você decide mudar de classe na próxima versão de seu programa e você precisa suportar ambos os formatos de serialização - o novo e o antigo (porque seus clientes ainda usam seus arquivos / bancos de dados antigos, ou eles se conectam a seu servidor usando uma versão antiga do seu produto). Mas você não pode mais usar este serializador, porque você usou
type.AssemblyQualifiedName
que parece
que contém seus atributos de montagem e versão ...
Agora, se você tentar alterar sua versão do assembly, ou decidir assiná-la, essa desserialização não vai funcionar ...
fonte
Eu fiz coisas semelhantes a isso. O que eu normalmente faço é ter certeza de que todos os atributos de serialização XML estão na classe concreta, e apenas fazer com que as propriedades dessa classe chamem as classes base (quando necessário) para recuperar informações que serão desserializadas quando o serializador chamar essas propriedades. É um pouco mais trabalhoso de codificação, mas funciona muito melhor do que tentar forçar o serializador a fazer a coisa certa.
fonte
Melhor ainda, usando notação:
[XmlRoot] public class MyClass { public abstract class MyAbstract {} public class MyInherited : MyAbstract {} [XmlArray(), XmlArrayItem(typeof(MyInherited))] public MyAbstract[] Items {get; set; } }
fonte