De juiste manier om IXmlSerializable te implementeren?

Zodra een programmeur besluit IXmlSerializablete implementeren, wat zijn dan de regels en best practices om dit te implementeren? Ik heb gehoord dat GetSchema()nullmoet retourneren en dat ReadXmlnaar het volgende element moet gaan voordat het terugkeert. Is dit waar? En hoe zit het met WriteXml– moet het een root-element voor het object schrijven of wordt aangenomen dat de root al is geschreven? Hoe moeten onderliggende objecten worden behandeld en geschreven?

Hier is een voorbeeld van wat ik nu heb. Ik zal het updaten als ik goede reacties krijg.

public class MyCalendar : IXmlSerializable
{
    private string _name;
    private bool _enabled;
    private Color _color;
    private List<MyEvent> _events = new List<MyEvent>();
    public XmlSchema GetSchema() { return null; }
    public void ReadXml(XmlReader reader)
    {
        if (reader.MoveToContent() == XmlNodeType.Element && reader.LocalName == "MyCalendar")
        {
            _name    = reader["Name"];
            _enabled = Boolean.Parse(reader["Enabled"]);
            _color   = Color.FromArgb(Int32.Parse(reader["Color"]));
            if (reader.ReadToDescendant("MyEvent"))
            {
                while (reader.MoveToContent() == XmlNodeType.Element && reader.LocalName == "MyEvent")
                {
                    MyEvent evt = new MyEvent();
                    evt.ReadXml(reader);
                    _events.Add(evt);
                }
            }
            reader.Read();
        }
    }
    public void WriteXml(XmlWriter writer)
    {
        writer.WriteAttributeString("Name",    _name);
        writer.WriteAttributeString("Enabled", _enabled.ToString());
        writer.WriteAttributeString("Color",   _color.ToArgb().ToString());
        foreach (MyEvent evt in _events)
        {
            writer.WriteStartElement("MyEvent");
            evt.WriteXml(writer);
            writer.WriteEndElement();
        }
    }
}
public class MyEvent : IXmlSerializable
{
    private string _title;
    private DateTime _start;
    private DateTime _stop;
    public XmlSchema GetSchema() { return null; }
    public void ReadXml(XmlReader reader)
    {
        if (reader.MoveToContent() == XmlNodeType.Element && reader.LocalName == "MyEvent")
        {
            _title = reader["Title"];
            _start = DateTime.FromBinary(Int64.Parse(reader["Start"]));
            _stop  = DateTime.FromBinary(Int64.Parse(reader["Stop"]));
            reader.Read();
        }
    }
    public void WriteXml(XmlWriter writer)
    {
        writer.WriteAttributeString("Title", _title);
        writer.WriteAttributeString("Start", _start.ToBinary().ToString());
        writer.WriteAttributeString("Stop",  _stop.ToBinary().ToString());
    }
}

Overeenkomstige voorbeeld-XML

<MyCalendar Name="Master Plan" Enabled="True" Color="-14069085">
    <MyEvent Title="Write Code" Start="-8589241828854775808" Stop="-8589241756854775808" />
    <MyEvent Title="???" Start="-8589241828854775808" Stop="-8589241756854775808" />
    <MyEvent Title="Profit!" Start="-8589247048854775808" Stop="-8589246976854775808" />
</MyCalendar>

Antwoord 1, autoriteit 100%

Ja, GetSchema() moet null retourneren .

IXmlSerializable.GetSchema Methode Dit
methode is gereserveerd en mag niet worden
gebruikt. Bij het implementeren van de
IXmlSerializable-interface, dat zou u moeten doen:
retourneer een null-referentie (Niets in
Visual Basic) van deze methode, en in plaats daarvan,
als het specificeren van een aangepast schema is
vereist, pas de toe
XmlSchemaProviderKenmerk aan de
klas.

Voor zowel lezen als schrijven is het objectelement al geschreven, dus u hoeft geen buitenste element toe te voegen aan write. U kunt bijvoorbeeld gewoon beginnen met het lezen/schrijven van attributen in de twee.

Voor schrijf:

De WriteXml-implementatie die u
bieden moet de XML wegschrijven
representatie van het object. De
framework schrijft een wrapper-element en
plaatst de XML-schrijver na zijn
begin. Uw implementatie kan schrijven
de inhoud ervan, inclusief kind
elementen. Het raamwerk wordt dan gesloten
het wrapper-element.

En voor lees:

De ReadXml-methode moet opnieuw worden samengesteld
uw object met behulp van de informatie die
is geschreven met de WriteXml-methode.

Als deze methode wordt aangeroepen, zal de lezer
staat aan het begin van de
element dat de informatie omhult voor
jouw type. Dat wil zeggen, net voor de
starttag die het begin aangeeft
van een geserialiseerd object. Wanneer dit
methode retourneert, moet het de . hebben gelezen
hele element van begin tot eind,
inclusief alle inhoud. in tegenstelling tot
de WriteXml-methode, het raamwerk
verwerkt het wikkelelement niet
automatisch. Uw implementatie
moet dat doen. Deze niet in acht nemen
positioneringsregels kunnen ervoor zorgen dat code
onverwachte runtime-uitzonderingen genereren
of corrupte gegevens.

Ik ben het ermee eens dat het een beetje onduidelijk is, maar het komt erop neer dat “het jouw taak is om Read()de tag met het eindelement van de wrapper”.


Antwoord 2, autoriteit 33%

Ik heb een artikel over dit onderwerp geschreven met voorbeelden, omdat de MSDN-documentatie nu nogal onduidelijk is en de voorbeelden die je op internet kunt vinden meestal onjuist zijn geïmplementeerd.

Valkuilen zijn het omgaan met locales en lege elementen naast wat Marc Gravell al noemde.

http://www.codeproject.com/KB/XML/ImplementIXmlSerializable.aspx


Antwoord 3, autoriteit 9%

Ja, het is allemaal een beetje een mijnenveld, nietwaar? Het antwoord van Marc Gravelldekt het grotendeels, maar ik wil hieraan toevoegen dat we het in een project waaraan ik werkte nogal onhandig vonden om het buitenste XML-element handmatig te schrijven. Het resulteerde ook in inconsistente XML-elementnamen voor objecten van hetzelfde type.

Onze oplossing was om onze eigen IXmlSerializable-interface te definiëren, afgeleid van de systeeminterface, die een methode toevoegde met de naam WriteOuterXml(). Zoals je kunt raden, zou deze methode gewoon het buitenste element schrijven, dan WriteXml()aanroepen en dan het einde van het element schrijven. Natuurlijk zou de XML-serializer van het systeem deze methode niet aanroepen, dus het was alleen nuttig als we onze eigen serialisatie deden, dus dat kan in jouw geval wel of niet nuttig zijn. Op dezelfde manier hebben we een methode ReadContentXml()toegevoegd, die niet het buitenste element leest, alleen de inhoud ervan.


Antwoord 4, autoriteit 2%

Als je al een XmlDocument-representatie van je klasse hebt of de XmlDocument-manier van werken met XML-structuren verkiest, is een snelle en vuile manier om IXmlSerializable te implementeren, deze xmldoc door te geven aan de verschillende functies.

WAARSCHUWING: XmlDocument (en/of XDocument) is een orde van grootte langzamer dan xmlreader/writer, dus als prestatie een absolute vereiste is, is deze oplossing niets voor u!

class ExampleBaseClass : IXmlSerializable { 
    public XmlDocument xmlDocument { get; set; }
    public XmlSchema GetSchema()
    {
        return null;
    }
    public void ReadXml(XmlReader reader)
    {
        xmlDocument.Load(reader);
    }
    public void WriteXml(XmlWriter writer)
    {
        xmlDocument.WriteTo(writer);
    }
}

Antwoord 5

De interface-implementatie wordt gedekt door de andere antwoorden, maar ik wilde mijn 2-cent ingooien voor het root-element.

Ik heb in het verleden geleerd om het root-element liever als metadata te gebruiken. Dit heeft een aantal voordelen:

  • Als er een null-object is, kan het nog steeds serialiseren
  • Vanuit het oogpunt van leesbaarheid van de code is het logisch

Hieronder ziet u een voorbeeld van een serialiseerbaar woordenboek waarin het hoofdelement van het woordenboek op die manier is gedefinieerd:

using System.Collections.Generic;
[System.Xml.Serialization.XmlRoot("dictionary")]
public partial class SerializableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, System.Xml.Serialization.IXmlSerializable
{
            public virtual System.Xml.Schema.XmlSchema GetSchema()
    {
        return null;
    }
    public virtual void ReadXml(System.Xml.XmlReader reader)
    {
        var keySerializer = new System.Xml.Serialization.XmlSerializer(typeof(TKey));
        var valueSerializer = new System.Xml.Serialization.XmlSerializer(typeof(TValue));
        bool wasEmpty = reader.IsEmptyElement;
        reader.Read();
        if (wasEmpty)
            return;
        while (reader.NodeType != System.Xml.XmlNodeType.EndElement)
        {
            reader.ReadStartElement("item");
            reader.ReadStartElement("key");
            TKey key = (TKey)keySerializer.Deserialize(reader);
            reader.ReadEndElement();
            reader.ReadStartElement("value");
            TValue value = (TValue)valueSerializer.Deserialize(reader);
            reader.ReadEndElement();
            Add(key, value);
            reader.ReadEndElement();
            reader.MoveToContent();
        }
        reader.ReadEndElement();
    }
    public virtual void WriteXml(System.Xml.XmlWriter writer)
    {
        var keySerializer = new System.Xml.Serialization.XmlSerializer(typeof(TKey));
        var valueSerializer = new System.Xml.Serialization.XmlSerializer(typeof(TValue));
        foreach (TKey key in Keys)
        {
            writer.WriteStartElement("item");
            writer.WriteStartElement("key");
            keySerializer.Serialize(writer, key);
            writer.WriteEndElement();
            writer.WriteStartElement("value");
            var value = this[key];
            valueSerializer.Serialize(writer, value);
            writer.WriteEndElement();
            writer.WriteEndElement();
        }
    }
    public SerializableDictionary() : base()
    {
    }
    public SerializableDictionary(IDictionary<TKey, TValue> dictionary) : base(dictionary)
    {
    }
    public SerializableDictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer) : base(dictionary, comparer)
    {
    }
    public SerializableDictionary(IEqualityComparer<TKey> comparer) : base(comparer)
    {
    }
    public SerializableDictionary(int capacity) : base(capacity)
    {
    }
    public SerializableDictionary(int capacity, IEqualityComparer<TKey> comparer) : base(capacity, comparer)
    {
    }
}

Other episodes