Das Problem ist gelöst!
OK, ich habe es also endlich geschafft (zugegebenermaßen mit einem Los der Hilfe von aquí !).
Fassen Sie also zusammen:
Ziele:
- Ich wollte nicht in die XmlInclude Route wegen der Wartungsprobleme.
- Sobald eine Lösung gefunden war, wollte ich, dass sie schnell in andere Anwendungen implementiert werden kann.
- Es können Sammlungen abstrakter Typen verwendet werden, aber auch einzelne abstrakte Eigenschaften.
- Ich wollte mir nicht die Mühe machen, in den konkreten Klassen "besondere" Dinge zu tun.
Identifizierte Probleme/zu beachtende Punkte:
- XmlSerializer einige ziemlich coole Reflexionen, aber es ist sehr begrenzt, wenn es um abstrakte Typen geht (d.h. es funktioniert nur mit Instanzen des abstrakten Typs selbst, nicht mit Unterklassen).
- Die Xml-Attributdekoratoren definieren, wie der XmlSerializer die gefundenen Eigenschaften behandelt. Der physikalische Typ kann auch angegeben werden, aber das erzeugt eine enge Kopplung zwischen der Klasse und dem Serialisierer (nicht gut).
- Wir können unseren eigenen XmlSerializer implementieren, indem wir eine Klasse erstellen, die Folgendes implementiert IXmlSerializable .
Die Lösung
Ich habe eine generische Klasse erstellt, in der Sie den generischen Typ als den abstrakten Typ angeben, mit dem Sie arbeiten werden. Dies gibt der Klasse die Möglichkeit, zwischen dem abstrakten Typ und dem konkreten Typ zu "übersetzen", da wir das Casting hart kodieren können (d.h. wir können mehr Informationen erhalten, als der XmlSerializer kann).
Ich habe dann die IXmlSerializable Schnittstelle ist dies ziemlich einfach, aber bei der Serialisierung müssen wir sicherstellen, dass wir den Typ der konkreten Klasse in die XML-Datei schreiben, damit wir ihn bei der De-Serialisierung zurückschreiben können. Es ist auch wichtig zu beachten, dass es sich um voll qualifiziert da die Versammlungen, in denen sich die beiden Klassen befinden, wahrscheinlich unterschiedlich sind. Es gibt natürlich eine kleine Typüberprüfung und andere Dinge, die hier passieren müssen.
Da der XmlSerializer nicht casten kann, müssen wir den Code dafür bereitstellen, so dass der implizite Operator dann überladen wird (ich wusste nicht einmal, dass man das tun kann!).
Der Code für den AbstractXmlSerializer lautet wie folgt:
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
}
}
Wie können wir also dem XmlSerializer mitteilen, dass er mit unserem Serializer und nicht mit dem Standard arbeiten soll? Wir müssen unseren Typ in der Eigenschaft type der Xml-Attribute übergeben, zum Beispiel:
[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>();
}
}
Hier können Sie sehen, dass wir eine Sammlung und eine einzelne Eigenschaft offenlegen, und alles, was wir tun müssen, ist das Hinzufügen der Typ benannten Parameter an die Xml-Deklaration an, ganz einfach! :D
HINWEIS: Wenn Sie diesen Code verwenden, würde ich mich sehr über einen Hinweis freuen. Es wird auch dazu beitragen, dass mehr Menschen die Community besuchen :)
Nun, ich weiß nicht, was ich mit den Antworten anfangen soll, da sie alle ihre Vor- und Nachteile haben. Ich werde die, die ich für nützlich halte, hochladen (nichts gegen die, die es nicht waren) und die Seite schließen, sobald ich die Antworten habe :)
Interessantes Problem und viel Spaß bei der Lösung! :)