web-dev-qa-db-fra.com

Moyen approprié d'implémenter IXmlSerializable?

Une fois qu'un programmeur décide d'implémenter IXmlSerializable, quelles sont les règles et les meilleures pratiques pour l'implémenter? J'ai entendu dire que GetSchema() devrait renvoyer null et ReadXml devrait passer à l'élément suivant avant de renvoyer. Est-ce vrai? Et que dire de WriteXml - devrait-il écrire un élément racine pour l’objet ou est-il supposé que la racine est déjà écrite? Comment les objets enfants doivent-ils être traités et écrits?

Voici un échantillon de ce que j'ai maintenant. Je vais le mettre à jour car j'ai de bonnes réponses.

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());
    }
}

Exemple de XML correspondant

<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>
146
Greg

Oui, GetSchema () doit renvoyer null .

IXmlSerializable.GetSchema, méthode Cette méthode est réservée et ne doit pas être utilisée. Lors de la mise en œuvre de l'interface IXmlSerializable, vous devez renvoyer une référence null (Nothing en Visual Basic) à partir de cette méthode et, si un schéma personnalisé est requis, appliquez XmlSchemaProviderAttribute à la classe.

L'élément object a déjà été écrit en lecture et en écriture. Il n'est donc pas nécessaire d'ajouter un élément externe en écriture. Par exemple, vous pouvez simplement commencer à lire/écrire des attributs dans les deux.

Pour écrire :

L'implémentation WriteXml que vous fournissez doit écrire la représentation XML de l'objet. La structure écrit un élément wrapper et positionne le rédacteur XML après son démarrage. Votre implémentation peut écrire son contenu, y compris des éléments enfants. Le cadre ferme ensuite l'élément enveloppant.

Et pour lire :

La méthode ReadXml doit reconstituer votre objet en utilisant les informations écrites par la méthode WriteXml.

Lorsque cette méthode est appelée, le lecteur est positionné au début de l'élément qui englobe les informations relatives à votre type. C'est-à-dire juste avant la balise de début qui indique le début d'un objet sérialisé. Lorsque cette méthode retourne, elle doit avoir lu la totalité de l'élément du début à la fin, y compris tout son contenu. Contrairement à la méthode WriteXml, le framework ne gère pas automatiquement l'élément wrapper. Votre implémentation doit le faire. Si vous ne respectez pas ces règles de positionnement, le code peut générer des exceptions d'exécution inattendues ou des données corrompues.

Je conviens que cela manque de clarté, mais cela revient à dire "votre tâche est de Read() la balise d'élément final de l'emballage".

93
Marc Gravell

J'ai écrit un article sur le sujet avec des exemples car la documentation MSDN est à présent assez floue et les exemples que vous pouvez trouver sur le Web sont la plupart du temps mal implémentés.

Les pièges sont le traitement des lieux et des éléments vides à côté de ce que Marc Gravell a déjà mentionné.

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

32
jdehaan

Oui, c'est un peu un champ de mines, n'est-ce pas? La réponse de Marc Gravell couvre assez bien la question, mais je voudrais ajouter que dans un projet sur lequel nous avons travaillé, nous trouvions très gênant de devoir écrire manuellement l’élément XML externe. Cela a également entraîné des noms d’éléments XML incohérents pour des objets du même type.

Notre solution a été de définir notre propre interface IXmlSerializable, dérivée de celle du système, qui a ajouté une méthode appelée WriteOuterXml(). Comme vous pouvez le deviner, cette méthode écrirait simplement l’élément externe, puis appellerait WriteXml(), puis écrirait la fin de l’élément. Bien sûr, le sérialiseur XML du système n'appelait pas cette méthode. Elle n'était donc utile que lorsque nous effectuions notre propre sérialisation, ce qui peut être utile ou non dans votre cas. De même, nous avons ajouté une méthode ReadContentXml(), qui ne lisait pas l'élément externe, mais uniquement son contenu.

8
EMP

Si vous avez déjà une représentation XmlDocument de votre classe ou préférez la façon dont XmlDocument fonctionne avec les structures XML, une façon rapide et sale d'implémenter IXmlSerializable consiste à simplement passer ce xmldoc aux différentes fonctions.

AVERTISSEMENT: XmlDocument (et/ou XDocument) est un ordre de grandeur plus lent que xmlreader/writer. Par conséquent, si les performances sont une exigence absolue, cette solution n'est pas pour vous!

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);
    }
}
2
Thijs Dalhuijsen