Problema risolto!
OK, quindi finalmente ci sono arrivato (devo ammettere che con molto aiuto da qui !).
Quindi riassumi:
Obiettivi:
- Non volevo scendere in XmlInclude percorso causa del mal di testa di manutenzione.
- Una volta trovata una soluzione, volevo che fosse rapida da implementare in altre applicazioni.
- È possibile utilizzare raccolte di tipi astratti, nonché singole proprietà astratte.
- Non volevo davvero preoccuparmi di dover fare cose "speciali" nelle classi concrete.
Problemi identificati / punti da notare:
- XmlSerializer fa alcune riflessioni piuttosto interessanti, ma è molto limitato quando si tratta di tipi astratti (cioè funzionerà solo con istanze del tipo astratto stesso, non con sottoclassi).
- I decoratori dell'attributo Xml definiscono il modo in cui XmlSerializer tratta le proprietà trovate. È anche possibile specificare il tipo fisico, ma questo crea un accoppiamento stretto tra la classe e il serializzatore (non buono).
- Possiamo implementare il nostro XmlSerializer creando una classe che implementa IXmlSerializable .
La soluzione
Ho creato una classe generica, in cui specifichi il tipo generico come tipo astratto con cui lavorerai. Questo 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 quante ne possa fare XmlSerializer).
Ho quindi implementato l' interfaccia IXmlSerializable , questo è abbastanza semplice, ma durante la serializzazione dobbiamo assicurarci di scrivere il tipo della classe concreta nell'XML, in modo da poterlo eseguire nuovamente durante la de-serializzazione. È anche importante notare che deve essere 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, è necessario fornire il codice per farlo, quindi l'operatore implicito viene quindi 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
{
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
}
}
Quindi, da lì, come facciamo a dire a XmlSerializer di lavorare con il nostro serializzatore piuttosto che con quello predefinito? Dobbiamo passare il nostro tipo all'interno della proprietà del tipo di 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 raccolta e una singola proprietà esposta e tutto ciò che dobbiamo fare è aggiungere il tipo parametro denominato di alla dichiarazione Xml, facile! : D
NOTA: se usi questo codice, apprezzerei davvero un grido. Aiuterà anche a guidare più persone verso la comunità :)
Ora, ma non sono sicuro di cosa fare con le risposte qui dato che avevano tutti i loro pro e contro. Modificherò quelli che ritengo utili (senza offesa per quelli che non lo erano) e lo chiuderò una volta che avrò il rappresentante :)
Problema interessante e buon divertimento da risolvere! :)