Problème résolu!
OK, alors j'y suis enfin arrivé (certes avec beaucoup d'aide d' ici !).
Alors résumez:
Buts:
- Je ne voulais pas emprunter la route XmlInclude à cause du mal de tête lié à la maintenance.
- Une fois qu'une solution a été trouvée, je voulais qu'elle soit rapide à mettre en œuvre dans d'autres applications.
- Des collections de types abstraits peuvent être utilisées, ainsi que des propriétés abstraites individuelles.
- Je ne voulais pas vraiment me soucier de devoir faire des choses "spéciales" dans les classes concrètes.
Problèmes identifiés / points à noter:
- XmlSerializer fait une réflexion assez cool, mais il est très limité en ce qui concerne les types abstraits (c'est-à-dire qu'il ne fonctionnera qu'avec des instances du type abstrait lui-même, pas des sous-classes).
- Les décorateurs d'attributs Xml définissent la manière dont XmlSerializer traite les propriétés de ses recherches. Le type physique peut également être spécifié, mais cela crée un couplage étroit entre la classe et le sérialiseur (pas bon).
- Nous pouvons implémenter notre propre XmlSerializer en créant une classe qui implémente IXmlSerializable .
La solution
J'ai créé une classe générique, dans laquelle vous spécifiez le type générique comme type abstrait avec lequel vous travaillerez. Cela donne à la classe la possibilité de "traduire" entre le type abstrait et le type concret puisque nous pouvons coder en dur le casting (c'est-à-dire que nous pouvons obtenir plus d'informations que le XmlSerializer).
J'ai ensuite implémenté l' interface IXmlSerializable , c'est assez simple, mais lors de la sérialisation, nous devons nous assurer que nous écrivons le type de la classe concrète dans le XML, afin de pouvoir le renvoyer lors de la désérialisation. Il est également important de noter qu'il doit être entièrement qualifié car les assemblys dans lesquels se trouvent les deux classes sont susceptibles de différer. Il y a bien sûr une petite vérification de type et des choses qui doivent se produire ici.
Étant donné que XmlSerializer ne peut pas effectuer de conversion, nous devons fournir le code pour le faire, de sorte que l'opérateur implicite est alors surchargé (je n'ai même jamais su que vous pouviez le faire!).
Le code pour AbstractXmlSerializer est le suivant:
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
}
}
Alors, à partir de là, comment dire au XmlSerializer de fonctionner avec notre sérialiseur plutôt qu'avec le sérialiseur par défaut? Nous devons passer notre type dans la propriété de type des attributs Xml, par exemple:
[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>();
}
}
Ici, vous pouvez voir, nous avons une collection et une seule propriété exposée, et tout ce que nous avons à faire est d'ajouter le paramètre de type nommé à la déclaration Xml, facile! :RÉ
REMARQUE: Si vous utilisez ce code, j'apprécierais vraiment un cri. Cela aidera également à attirer plus de personnes dans la communauté :)
Maintenant, mais je ne sais pas quoi faire avec les réponses ici, car ils avaient tous leurs avantages et leurs inconvénients. Je vais améliorer ceux que je trouve utiles (sans offenser ceux qui ne le sont pas) et fermer cela une fois que j'ai le représentant :)
Problème intéressant et très amusant à résoudre! :)