Frage

Im Anschluss an meine vorherige Frage Ich habe daran gearbeitet, mein Objektmodell in XML zu serialisieren.Aber ich bin jetzt auf ein Problem gestoßen (welche Überraschung!).

Das Problem, das ich habe, ist, dass ich eine Sammlung habe, die einen abstrakten Basisklassentyp hat und mit den konkreten abgeleiteten Typen gefüllt ist.

Ich dachte, es wäre in Ordnung, einfach die XML-Attribute zu allen beteiligten Klassen hinzuzufügen, und alles wäre perfekt.Leider ist das nicht der Fall!

Also habe ich ein wenig bei Google recherchiert und jetzt verstehe ich es Warum es funktioniert nicht.Darin Die XmlSerializer führt tatsächlich einige clevere Überlegungen durch, um Objekte in/aus XML zu serialisieren, und da es auf dem abstrakten Typ basiert, kann es nicht herausfinden, womit zum Teufel es spricht.Bußgeld.

Ich bin darauf gestoßen diese Seite auf CodeProject, was sehr hilfreich zu sein scheint (noch nicht vollständig gelesen/konsumiert), aber ich dachte, ich würde dieses Problem auch gerne auf die StackOverflow-Tabelle übertragen, um zu sehen, ob Sie irgendwelche netten Hacks/Tricks dafür haben Bringen Sie dies auf die schnellste/leichteste Art und Weise zum Laufen.

Eine Sache, die ich noch hinzufügen sollte, ist, dass ich NICHT Ich möchte hinuntergehen XmlInclude Route.Es gibt einfach zu viele Kopplungen und dieser Bereich des Systems befindet sich in der intensiven Entwicklung, sodass die Wartung zu echten Kopfschmerzen führen würde!

War es hilfreich?

Lösung

Problem gelöst!

OK, also ich habe es endlich geschafft (zugegebenermaßen mit einem viel der Hilfe von Hier!).

Fassen Sie also zusammen:

Ziele:

  • Ich wollte nicht runtergehen XmlInclude Route aufgrund der Wartungsprobleme.
  • Sobald eine Lösung gefunden war, wollte ich, dass sie schnell in anderen Anwendungen implementiert werden kann.
  • Es können Sammlungen abstrakter Typen sowie einzelne abstrakte Eigenschaften verwendet werden.
  • Ich wollte mich nicht wirklich darum kümmern, in den konkreten Kursen „besondere“ Dinge tun zu müssen.

Identifizierte Probleme/zu beachtende Punkte:

  • XmlSerializer macht eine ziemlich coole Reflexion, aber das ist es 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.Es kann auch der physikalische Typ angegeben werden, allerdings entsteht dadurch ein enge Kopplung zwischen der Klasse und dem Serializer (nicht gut).
  • Wir können unseren eigenen XmlSerializer implementieren, indem wir eine Klasse erstellen, die ihn implementiert IXmlSerialisierbar .

Die Lösung

Ich habe eine generische Klasse erstellt, in der Sie den generischen Typ als 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 die Umwandlung fest codieren können (d. h.Wir können mehr Informationen erhalten als der XmlSerializer.

Das habe ich dann umgesetzt IXmlSerialisierbar Schnittstelle, das 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 Deserialisieren zurückwandeln können.Es ist auch wichtig zu beachten, dass dies der Fall sein muss vollqualifiziert da die Versammlungen, in denen sich die beiden Klassen befinden, wahrscheinlich unterschiedlich sind.Hier gibt es natürlich eine kleine Typprüfung und andere Dinge, die passieren müssen.

Da der XmlSerializer nicht umwandeln kann, müssen wir den Code dafür bereitstellen, damit der implizite Operator dann überladen wird (ich wusste nicht einmal, dass man das kann!).

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 also den XmlSerializer anweisen, mit unserem Serializer und nicht mit dem Standard zu arbeiten?Wir müssen unseren Typ innerhalb der XML-Attribute-Typ-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 sehen Sie, wir haben eine Sammlung und eine einzelne Eigenschaft, die verfügbar gemacht wird, und alles, was wir tun müssen, ist, diese hinzuzufügen Typ Benannten Parameter in die XML-Deklaration einfügen, ganz einfach!:D

NOTIZ:Wenn Sie diesen Code verwenden, würde ich mich sehr über einen Hinweis freuen.Es wird auch dazu beitragen, mehr Menschen in die Community zu locken :)

Nun, aber ich bin mir nicht sicher, was ich mit den Antworten hier anfangen soll, da sie alle ihre Vor- und Nachteile hatten.Ich werde diejenigen aktualisieren, die ich für nützlich halte (keine Beleidigung für diejenigen, die es nicht waren) und dies schließen, sobald ich den Repräsentanten habe :)

Interessantes Problem und es macht viel Spaß, es zu lösen!:) :)

Andere Tipps

Zu beachten ist die Tatsache, dass Sie im XmlSerialiser-Konstruktor ein Array von Typen übergeben können, die der Serialisierer möglicherweise nur schwer auflösen kann.Ich musste das schon einige Male verwenden, wenn eine Sammlung oder ein komplexer Satz von Datenstrukturen serialisiert werden musste und diese Typen in verschiedenen Baugruppen usw. lebten.

XmlSerialiser-Konstruktor mit extraTypes-Parametern

BEARBEITEN:Ich möchte hinzufügen, dass dieser Ansatz gegenüber XmlInclude-Attributen usw. den Vorteil hat, dass Sie eine Möglichkeit finden können, zur Laufzeit eine Liste Ihrer möglichen konkreten Typen zu entdecken, zu kompilieren und diese einzufügen.

Im Ernst, ein erweiterbares POCO-Framework wird niemals zuverlässig in XML serialisieren.Ich sage das, weil ich garantieren kann, dass jemand vorbeikommt, Ihren Unterricht verlängert und es vermasselt.

Sie sollten die Verwendung von XAML zur Serialisierung Ihrer Objektdiagramme in Betracht ziehen.Es ist dafür konzipiert, die XML-Serialisierung hingegen nicht.

Der Xaml-Serialisierer und -Deserialisierer verarbeitet problemlos Generika, Sammlungen von Basisklassen und Schnittstellen (sofern die Sammlungen selbst implementieren). IList oder IDictionary).Es gibt einige Einschränkungen, z. B. das Markieren Ihrer schreibgeschützten Sammlungseigenschaften mit dem DesignerSerializationAttribute, aber die Überarbeitung Ihres Codes zur Behandlung dieser Eckfälle ist nicht so schwierig.

Nur ein kurzes Update dazu, ich habe es nicht vergessen!

Ich mache nur noch ein bisschen Recherche, es sieht so aus, als ob ich auf der Suche nach einem Gewinner bin, ich muss nur noch den Code sortieren.

Bisher habe ich Folgendes:

  • Der XmlSeralizer ist im Grunde eine Klasse, die geschickt über die Klassen nachdenkt, die sie serialisiert.Es bestimmt die Eigenschaften, die basierend auf serialisiert werden Typ.
  • Der Grund für das Problem liegt darin, dass eine Typinkongruenz vorliegt, die erwartet wird Basistyp aber tatsächlich erhält die Abgeleiteter Typ ..Während Sie vielleicht denken, dass es polymorph behandelt würde, ist dies nicht der Fall, da es eine ganze zusätzliche Menge an Reflexion und Typprüfung erfordern würde, wofür es nicht konzipiert ist.

Dieses Verhalten scheint überschrieben werden zu können (Code steht aus), indem eine Proxy-Klasse erstellt wird, die als Vermittler für den Serialisierer fungiert.Dadurch wird im Wesentlichen der Typ der abgeleiteten Klasse bestimmt und dieser dann wie gewohnt serialisiert.Diese Proxy-Klasse leitet dann das XML-Backup der Zeile an den Hauptserialisierer weiter.

Beobachten Sie diesen Raum!^_^

Es ist sicherlich eine Lösung für Ihr Problem, aber es gibt noch ein anderes Problem, das Ihre Absicht, ein „portables“ XML-Format zu verwenden, etwas untergräbt.Schlimme Dinge passieren, wenn Sie sich entscheiden, Klassen in der nächsten Version Ihres Programms zu ändern, und Sie beide Serialisierungsformate unterstützen müssen – das neue und das alte (da Ihre Clients immer noch ihre alten Dateien/Datenbanken verwenden oder eine Verbindung herstellen). Ihr Server verwendet eine alte Version Ihres Produkts).Sie können diesen Serialisierer jedoch nicht mehr verwenden, da Sie ihn verwendet haben

type.AssemblyQualifiedName

was aussieht

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

das heißt, enthält Ihre Assembly-Attribute und Version ...

Wenn Sie nun versuchen, Ihre Assembly-Version zu ändern, oder sich entscheiden, sie zu signieren, wird diese Deserialisierung nicht funktionieren ...

Ich habe ähnliche Dinge getan.Normalerweise stelle ich sicher, dass sich alle XML-Serialisierungsattribute in der konkreten Klasse befinden, und lasse nur die Eigenschaften dieser Klasse an die Basisklassen weiterleiten (sofern erforderlich), um Informationen abzurufen, die de/serialisiert werden, wenn der Serialisierer aufruft diese Eigenschaften.Es ist zwar etwas mehr Programmierarbeit, aber es funktioniert viel besser als der Versuch, den Serialisierer dazu zu zwingen, genau das Richtige zu tun.

Noch besser: Verwenden Sie die Notation:

[XmlRoot]
public class MyClass {
    public abstract class MyAbstract {} 
    public class MyInherited : MyAbstract {} 
    [XmlArray(), XmlArrayItem(typeof(MyInherited))] 
    public MyAbstract[] Items {get; set; } 
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top