Domanda

Seguendo il mio domanda precedente Ho lavorato per serializzare il mio modello a oggetti in XML.Ma ora ho riscontrato un problema (quelle sorpresa!).

Il problema che ho è che ho una raccolta, che è di un tipo di classe base astratta, popolata dai tipi derivati ​​concreti.

Ho pensato che sarebbe andato bene aggiungere semplicemente gli attributi XML a tutte le classi coinvolte e tutto sarebbe andato bene.Purtroppo non è così!

Quindi ho fatto qualche ricerca su Google e ora ho capito Perché la sua non funziona.In ciò IL XmlSerializer sta infatti facendo qualche riflessione intelligente per serializzare gli oggetti in/da XML e, poiché è basato sul tipo astratto, non riesce a capire con cosa diavolo sta parlando.Bene.

Mi sono imbattuto questa pagina su CodeProject, che sembra possa essere di grande aiuto (ancora da leggere/consumare completamente), ma ho pensato che mi piacerebbe portare questo problema anche alla tabella StackOverflow, per vedere se hai qualche hack/trucco accurato per farlo farlo funzionare nel modo più rapido/leggero possibile.

Una cosa che dovrei anche aggiungere è che io NON voglio andare giù per il XmlInclude itinerario.C'è semplicemente troppo accoppiamento con esso, e quest'area del sistema è in fase di sviluppo pesante, quindi sarebbe un vero grattacapo in termini di manutenzione!

È stato utile?

Soluzione

Problema risolto!

OK, quindi finalmente ci sono arrivato (certamente con a quantità di aiuto da Qui!).

Quindi riassumo:

Obiettivi:

  • Non volevo scendere XmlInclude percorso a causa del mal di testa di manutenzione.
  • Una volta trovata una soluzione, volevo che fosse rapidamente implementabile in altre applicazioni.
  • È possibile utilizzare raccolte di tipi astratti, nonché singole proprietà astratte.
  • Non volevo davvero preoccuparmi di dover fare cose "speciali" nelle lezioni concrete.

Problemi identificati/punti da notare:

  • XmlSerializer fa qualche riflessione piuttosto interessante, ma lo è molto limitato quando si tratta di tipi astratti (ad es.funzionerà solo con istanze del tipo astratto stesso, non con sottoclassi).
  • I decoratori di attributi Xml definiscono il modo in cui XmlSerializer tratta le proprietà trovate.È possibile specificare anche il tipo fisico, ma ciò crea un file accoppiamento stretto tra la classe e il serializzatore (non va bene).
  • Possiamo implementare il nostro XmlSerializer creando una classe che implementa IXmlSerializzabile .

La soluzione

Ho creato una classe generica, in cui specifichi il tipo generico come tipo astratto con cui lavorerai.Ciò dà alla classe la capacità di "tradurre" tra il tipo astratto e il tipo concreto poiché possiamo codificare il casting (cioèpossiamo ottenere più informazioni di quelle che può ottenere XmlSerializer).

Ho quindi implementato il file IXmlSerializzabile interfaccia, questo è piuttosto semplice, ma durante la serializzazione dobbiamo assicurarci di scrivere il tipo della classe concreta nell'XML, in modo da poterlo ripristinare durante la deserializzazione.È anche importante notare che deve esserlo pienamente qualificato poiché è probabile che le assemblee in cui si trovano le due classi differiscano.Ovviamente c'è un piccolo controllo del tipo e cose che devono accadere qui.

Poiché XmlSerializer non può eseguire il cast, dobbiamo fornire il codice per farlo, in modo che l'operatore implicito venga sovraccaricato (non sapevo nemmeno che potessi farlo!).

Il codice per AbstractXmlSerializer è questo:

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
    }
}

Quindi, da lì, come possiamo dire a XmlSerializer di funzionare con il nostro serializzatore anziché con quello predefinito?Dobbiamo passare il nostro tipo all'interno della proprietà del tipo attributi Xml, ad esempio:

[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>();
    }
}

Qui puoi vedere che abbiamo una collezione e una singola proprietà esposta e tutto ciò che dobbiamo fare è aggiungere il file tipo parametro denominato nella dichiarazione Xml, facile!:D

NOTA:Se usi questo codice, apprezzerei davvero un ringraziamento.Aiuterà anche ad attirare più persone nella comunità :)

Ora, ma non sono sicuro di cosa fare con le risposte qui poiché tutte avevano i loro pro e contro.Modificherò quelli che ritengo siano stati utili (senza offesa per quelli che non lo erano) e chiuderò il tutto una volta che avrò il rappresentante :)

Problema interessante e divertente da risolvere!:)

Altri suggerimenti

Una cosa da considerare è il fatto che nel costruttore XmlSerialiser è possibile passare un array di tipi che il serializzatore potrebbe avere difficoltà a risolvere.Ho dovuto usarlo parecchie volte in cui una raccolta o un insieme complesso di strutture dati doveva essere serializzato e quei tipi vivevano in assiemi diversi, ecc.

Costruttore XmlSerialiser con param. extraTypes

MODIFICARE:Vorrei aggiungere che questo approccio ha il vantaggio rispetto agli attributi XmlInclude ecc. di poter trovare un modo per scoprire e compilare un elenco dei possibili tipi concreti in fase di esecuzione e inserirli.

Seriamente, un framework estensibile di POCO non sarà mai serializzato in XML in modo affidabile.Dico questo perché posso garantire che qualcuno verrà, estenderà la tua lezione e rovinerà tutto.

Dovresti esaminare l'utilizzo di XAML per serializzare i grafici degli oggetti.È progettato per fare questo, mentre la serializzazione XML non lo è.

Il serializzatore e deserializzatore Xaml gestisce senza problemi i generici, anche le raccolte di classi base e le interfacce (a condizione che le raccolte stesse implementino IList O IDictionary).Ci sono alcuni avvertimenti, come contrassegnare le proprietà della raccolta di sola lettura con il simbolo DesignerSerializationAttribute, ma rielaborare il codice per gestire questi casi limite non è così difficile.

Solo un breve aggiornamento su questo, non l'ho dimenticato!

Sto solo facendo qualche altra ricerca, sembra che io abbia trovato un vincitore, devo solo sistemare il codice.

Finora, ho quanto segue:

  • IL XmlSeralizer è fondamentalmente una classe che fa alcune riflessioni interessanti sulle classi che sta serializzando.Determina le proprietà serializzate in base a Tipo.
  • Il motivo per cui si verifica il problema è perché si sta verificando una mancata corrispondenza del tipo, si prevede il file BaseType ma in realtà riceve il Tipo derivato ..Anche se potresti pensare che lo tratterebbe in modo polimorfico, non è così poiché comporterebbe un carico extra di riflessione e controllo del tipo, cosa per cui non è progettato.

Sembra che questo comportamento possa essere sovrascritto (codice in sospeso) creando una classe proxy che funga da intermediario per il serializzatore.Ciò determinerà sostanzialmente il tipo della classe derivata e quindi la serializzerà normalmente.Questa classe proxy quindi alimenterà l'XML di backup della linea al serializzatore principale.

Guarda questo spazio!^_^

È certamente una soluzione al tuo problema, ma c'è un altro problema, che in qualche modo mina la tua intenzione di utilizzare il formato XML "portatile".Succede una cosa brutta quando decidi di cambiare classe nella prossima versione del tuo programma e hai bisogno di supportare entrambi i formati di serializzazione - quello nuovo e quello vecchio (perché i tuoi clienti usano ancora i loro vecchi file/database, o si connettono a il tuo server utilizzando la vecchia versione del tuo prodotto).Ma non puoi più utilizzare questo serializzatore, perché hai usato

type.AssemblyQualifiedName

che assomiglia

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

che contiene gli attributi e la versione dell'assembly...

Ora, se provi a modificare la versione dell'assembly o decidi di firmarla, questa deserializzazione non funzionerà...

Ho fatto cose simili a questa.Quello che faccio normalmente è assicurarmi che tutti gli attributi di serializzazione XML siano sulla classe concreta e che le proprietà di quella classe chiamino semplicemente le classi base (dove richiesto) per recuperare informazioni che verranno de/serializzate quando il serializzatore richiama quelle proprietà.È un po' più di lavoro di codifica, ma funziona molto meglio che tentare di forzare il serializzatore a fare semplicemente la cosa giusta.

Ancora meglio, usando la notazione:

[XmlRoot]
public class MyClass {
    public abstract class MyAbstract {} 
    public class MyInherited : MyAbstract {} 
    [XmlArray(), XmlArrayItem(typeof(MyInherited))] 
    public MyAbstract[] Items {get; set; } 
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top