Pergunta

Na sequência do meu pergunta anterior Tenho trabalhado para serializar meu modelo de objeto em XML.Mas agora me deparei com 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 bom apenas adicionar os atributos XML a todas as classes envolvidas e tudo ficaria ótimo.Infelizmente, esse não é o caso!

Então, fiz algumas pesquisas no Google e agora entendo por que não está funcionando.Naquilo o XmlSerializer está de fato fazendo algumas reflexões inteligentes para serializar objetos de/para XML e, como é baseado no tipo abstrato, ele não consegue descobrir com o que diabos está falando.Multar.

eu me deparei esta página no CodeProject, que parece que pode ajudar muito (ainda para ler/consumir totalmente), mas pensei em trazer esse problema para a tabela StackOverflow também, para ver se você tem algum hack/truque legal para coloque isso em funcionamento da maneira mais rápida/leve possível.

Uma coisa que devo acrescentar também é que NÃO quero descer XmlInclude rota.Há simplesmente muito acoplamento com ele, e esta área do sistema está sob forte desenvolvimento, então seria uma verdadeira dor de cabeça para manutenção!

Foi útil?

Solução

Problema resolvido!

OK, finalmente cheguei lá (reconheço com um muito de ajuda de aqui!).

Então resumir:

Metas:

  • eu não queria descer XmlIncluir rota devido à dor de cabeça de manutenção.
  • Assim que uma solução fosse encontrada, eu queria que ela fosse implementada rapidamente em outras aplicações.
  • Coleções de tipos abstratos podem ser usadas, bem como propriedades abstratas individuais.
  • Eu realmente não queria me preocupar em fazer coisas "especiais" nas aulas concretas.

Problemas/pontos identificados a serem observados:

  • XMLSerializador faz uma reflexão bem legal, mas é muito limitado quando se trata de tipos abstratos (ou seja,funcionará apenas com instâncias do próprio tipo abstrato, não com subclasses).
  • Os decoradores de atributos Xml definem como o XmlSerializer trata as propriedades que encontra.O tipo físico também pode ser especificado, mas isso cria um acoplamento apertado entre a classe e o serializador (não é bom).
  • Podemos implementar nosso próprio XmlSerializer criando uma classe que implemente IXmlSerializável .

A solução

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, já que podemos codificar a conversão (ou seja,podemos obter mais informações do que o XmlSerializer).

Eu então implementei o IXmlSerializável interface, isso é bastante simples, mas ao serializar precisamos garantir que escrevemos o tipo da classe concreta no XML, para que possamos convertê-lo de volta ao desserializar.Também é importante ressaltar que deve ser completamente qualificado já que as montagens 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 ser convertido, precisamos fornecer o código para fazer isso, para que o operador implícito fique sobrecarregado (eu nem sabia que você poderia fazer isso!).

O código para o 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 dizemos ao XmlSerializer para funcionar com nosso serializador em vez do padrão?Devemos passar nosso tipo dentro da propriedade type dos 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 que temos uma coleção e uma única propriedade sendo expostas, e tudo o que precisamos fazer é adicionar o tipo parâmetro nomeado para a declaração XML, fácil!:D

OBSERVAÇÃO:Se você usar este código, eu realmente apreciaria uma mensagem.Também ajudará a atrair mais pessoas para a comunidade :)

Agora, mas não tenho certeza sobre o que fazer com as respostas aqui, já que todas tinham seus prós e contras.Vou atualizar aqueles que considero úteis (sem ofensa para aqueles que não foram) e encerrar assim que tiver o representante :)

Problema interessante e divertido de resolver!:)

Outras dicas

Uma coisa a observar é o fato de que no construtor XmlSerialiser você pode passar uma matriz de tipos que o serializador pode estar tendo dificuldade para 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 assemblies diferentes, etc.

Construtor XmlSerialiser com parâmetro extraTypes

EDITAR:Eu acrescentaria que essa abordagem tem o benefício sobre os atributos XmlInclude, etc., pois você pode descobrir uma maneira de descobrir e compilar uma lista de seus possíveis tipos concretos em tempo de execução e inseri-los.

Sério, uma estrutura extensível de POCOs nunca será serializada para XML de maneira confiável.Digo isso porque posso garantir que alguém vai aparecer, estender sua aula e estragar tudo.

Você deve usar XAML para serializar seus gráficos de objetos.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 (desde que as próprias coleções implementem IList ou IDictionary).Existem algumas advertências, como marcar as propriedades da coleção somente leitura com o DesignerSerializationAttribute, mas retrabalhar seu código para lidar com esses casos extremos não é tão difícil.

Apenas uma rápida atualização sobre isso, não esqueci!

Apenas fazendo mais pesquisas, parece que estou no caminho certo, só preciso classificar o código.

Até agora, tenho o seguinte:

  • O XmlSeralizer é basicamente uma classe que faz algumas reflexões interessantes sobre as classes que está serializando.Ele determina as propriedades que são serializadas com base no Tipo.
  • A razão pela qual o problema ocorre é porque está ocorrendo uma incompatibilidade de tipo, ele está esperando o TipoBase mas na verdade recebe o Tipo Derivado ..Embora você possa pensar que isso seria tratado polimorficamente, isso não acontece, pois envolveria uma carga extra de reflexão e verificação de tipo, o que não foi projetado para fazer.

Esse comportamento parece poder ser substituído (código pendente) criando uma classe proxy para atuar como intermediária para o serializador.Isso basicamente determinará o tipo da classe derivada e a serializará normalmente.Essa classe de proxy alimentará esse XML de volta na linha para o serializador principal.

Assista esse espaço!^_^

Certamente é uma solução para o seu problema, mas há outro problema, que prejudica um pouco a sua intenção de usar o formato XML "portátil".Uma coisa ruim acontece quando você decide mudar de classe na próxima versão do seu programa e 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 se conectam a seu servidor usando uma versão antiga do seu produto).Mas você não pode mais usar esse serializador, porque você usou

type.AssemblyQualifiedName

que parece

TopNamespace.SubNameSpace.ContainingClass+NestedClass, MyAssembly, Version=1.3.0.0, Culture=neutral, PublicKeyToken=b17a5c561934e089

que contém seus atributos e versão do assembly ...

Agora se você tentar alterar sua versão do assembly, ou decidir assiná-lo, essa desserialização não vai funcionar...

Eu fiz coisas semelhantes a isso.O que normalmente faço é garantir que todos os atributos de serialização XML estejam na classe concreta e apenas fazer com que as propriedades dessa classe sejam chamadas para 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.

Melhor ainda, usando a notação:

[XmlRoot]
public class MyClass {
    public abstract class MyAbstract {} 
    public class MyInherited : MyAbstract {} 
    [XmlArray(), XmlArrayItem(typeof(MyInherited))] 
    public MyAbstract[] Items {get; set; } 
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top