Problem gelöst!
OK, also bin ich endlich da (zugegebenermaßen mit viel Hilfe von hier !).
Fassen Sie also zusammen:
Tore:
- Ich wollte die XmlInclude- Route wegen der Wartungskopfschmerzen nicht gehen .
- Sobald eine Lösung gefunden wurde, wollte ich, dass sie schnell in andere Anwendungen implementiert werden kann.
- Es können Sammlungen von abstrakten Typen sowie einzelne abstrakte Eigenschaften verwendet werden.
- Ich wollte mich nicht wirklich darum kümmern, "besondere" Dinge in den konkreten Klassen tun zu müssen.
Identifizierte Probleme / zu beachtende Punkte:
- XmlSerializer macht einige ziemlich coole Reflexionen, aber es ist sehr begrenzt, wenn es um abstrakte Typen geht (dh 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 ebenfalls angegeben werden, dies schafft jedoch eine enge Kopplung zwischen der Klasse und dem Serializer (nicht gut).
- Wir können unseren eigenen XmlSerializer implementieren, indem wir eine Klasse erstellen, die IXmlSerializable implementiert .
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 codieren können (dh wir können mehr Informationen erhalten als der XmlSerializer).
Ich habe dann die IXmlSerializable- Schnittstelle implementiert , dies ist ziemlich einfach, aber beim Serialisieren müssen wir sicherstellen, dass wir den Typ der konkreten Klasse in das XML schreiben, damit wir ihn beim De-Serialisieren zurücksetzen können. Es ist auch wichtig zu beachten, dass es vollständig qualifiziert sein muss, da sich die Baugruppen, in denen sich die beiden Klassen befinden, wahrscheinlich unterscheiden. Es gibt natürlich eine kleine Typprüfung und Dinge, die hier passieren müssen.
Da der XmlSerializer nicht umgewandelt werden kann, müssen wir den Code dafür bereitstellen, damit der implizite Operator dann überladen wird (ich wusste nicht einmal, dass Sie dies tun können!).
Der Code für den AbstractXmlSerializer lautet:
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 den XmlSerializer von dort aus anweisen, mit unserem Serializer und nicht mit dem Standard zu arbeiten? Wir müssen unseren Typ innerhalb der Xml-Attribute type-Eigenschaft ü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 eine Sammlung und eine einzelne Eigenschaft verfügbar gemacht werden. Alles, was wir tun müssen, ist , der XML-Deklaration den Parameter type namens hinzuzufügen. : D.
HINWEIS: Wenn Sie diesen Code verwenden, würde ich mich sehr über ein Shout-Out freuen. Es wird auch helfen, mehr Leute in die Community zu bringen :)
Nun, aber unsicher, was wir mit den Antworten hier anfangen sollen, da sie alle ihre Vor- und Nachteile hatten. Ich werde diejenigen aufrüsten, die ich für nützlich halte (keine Beleidigung für diejenigen, die es nicht waren) und dies abschließen, sobald ich den Repräsentanten habe :)
Interessantes Problem und viel Spaß zu lösen! :) :)