73 votes

Pourquoi n'y a-t-il pas de dictionnaire sérialisable par XML dans .NET ?

J'ai besoin d'un dictionnaire sérialisable par XML. En fait, j'ai maintenant deux programmes très différents qui en ont besoin. J'ai été plutôt surpris de constater que .NET n'en possède pas. J'ai posé la question ailleurs et j'ai reçu des réponses sarcastiques. Je ne comprends pas pourquoi il s'agit d'une question stupide.

Quelqu'un peut-il m'expliquer pourquoi il n'existe pas de dictionnaire sérialisable au format XML, étant donné la dépendance de diverses fonctionnalités de .NET vis-à-vis de la sérialisation XML ? J'espère que vous pourrez également m'expliquer pourquoi certaines personnes considèrent que c'est une question stupide. Je suppose qu'il me manque quelque chose de fondamental et j'espère que vous pourrez combler les lacunes.

5 votes

La question est incorrecte, car elle ne tient pas compte de la cause et de l'effet. La question devrait être : "pourquoi XmlSerializer ne peut pas sérialiser les dictionnaires" ? Parce qu'il y a de nombreuses façons de faire de la sérialisation XML dans .NET, et la plupart d'entre elles sérialisent très bien les dictionnaires ( DataContractSerializer , SoapFormatter ...).

0 votes

Je suppose que vous n'avez pas examiné "XmlDictionaryWriter.CreateDictionaryWriter"... ou les 100 autres façons de sérialiser les dictionnaires dans .NET (certaines d'entre elles sont intégrées). ...Par ailleurs, pourquoi avez-vous besoin d'un dictionnaire ? J'ai toujours trouvé que les objets à typage fort fonctionnaient mieux, pourquoi ne pas simplement implémenter une classe avec un [DataContract], et IExtensibleDataObject ?

0 votes

Quelles sont les fonctionnalités modernes de .NET qui, selon vous, dépendent de la sérialisation XML ? Les fichiers de configuration n'utilisent pas la sérialisation, et les services web ASMX ne sont destinés qu'à une utilisation patrimoniale. (déplacé dans le commentaire de la réponse)

53voto

Loudenvier Points 2390

Je sais qu'on a déjà répondu à cette question, mais comme j'ai une méthode très concise (code) pour faire la sérialisation d'IDictionary avec la classe DataContractSerializer (utilisée par WCF, mais qui pourrait et devrait être utilisée n'importe où), je n'ai pas pu résister à l'envie de la contribuer ici :

public static class SerializationExtensions
{
    public static string Serialize<T>(this T obj)
    {
        var serializer = new DataContractSerializer(obj.GetType());
        using (var writer = new StringWriter())
        using (var stm = new XmlTextWriter(writer))
        {
            serializer.WriteObject(stm, obj);
            return writer.ToString();
        }
    }
    public static T Deserialize<T>(this string serialized)
    {
        var serializer = new DataContractSerializer(typeof(T));
        using (var reader = new StringReader(serialized))
        using (var stm = new XmlTextReader(reader))
        {
            return (T)serializer.ReadObject(stm);
        }
    }
}

Cela fonctionne parfaitement dans .NET 4 et devrait également fonctionner dans .NET 3.5, bien que je ne l'aie pas encore testé.

UPDATE : Il n'a pas fonctionne dans .NET Compact Framework (même pas NETCF 3.7 pour Windows Phone 7), car les DataContractSerializer n'est pas pris en charge !

J'ai fait le streaming vers les chaînes de caractères parce que c'était plus pratique pour moi, bien que j'aurais pu introduire une sérialisation de niveau inférieur à Stream et ensuite l'utiliser pour sérialiser vers les chaînes de caractères, mais j'ai tendance à généraliser seulement quand c'est nécessaire (tout comme l'optimisation prématurée est diabolique, il en est de même pour la généralisation prématurée...)

L'utilisation est très simple :

// dictionary to serialize to string
Dictionary<string, object> myDict = new Dictionary<string, object>();
// add items to the dictionary...
myDict.Add(...);
// serialization is straight-forward
string serialized = myDict.Serialize();
...
// deserialization is just as simple
Dictionary<string, object> myDictCopy = 
    serialized.Deserialize<Dictionary<string,object>>();

myDictCopy sera une copie verbatim de myDict.

Vous remarquerez également que les méthodes génériques fournies pourront sérialiser n'importe quel type (à ma connaissance) puisque ce n'est pas limité aux interfaces IDictionary, ce peut être vraiment n'importe quel type générique T.

J'espère que cela aidera quelqu'un d'autre !

5 votes

Fonctionne très bien ! Pour les autres développeurs : Vous devrez ajouter une référence de projet pour System.Runtime.Serialization si vous n'en avez pas déjà un, mais il est disponible dans le profil client .NET 4.0.

0 votes

Il n'a pas fonctionné avec le SDK Windows Phone 8 ciblant Windows Phone 7.5 (qui est Silverlight 3).

0 votes

@Adarsha Selon la documentation de DataContractSerializer, il prend en charge les plates-formes suivantes : Windows 8, Windows Server 2012, Windows 7, Windows Vista SP2, Windows Server 2008 (Server Core Role non pris en charge), Windows Server 2008 R2 (Server Core Role pris en charge avec SP1 ou une version ultérieure ; Itanium non pris en charge)... Le SDK du téléphone n'est pas mentionné... Windows Phone 7 utilise .NET Compact Framework 3.7, donc pas de DataContractSerializer :-( J'ai mis à jour le post en conséquence pour que les gens ne perdent pas de temps à comprendre ce qui n'a pas fonctionné ! Merci Adarsha !

15voto

John Saunders Points 118808

La sérialisation XML ne consiste pas seulement à créer un flux d'octets. Il s'agit également de créer un schéma XML sur lequel ce flux d'octets sera validé. Il n'y a pas de bonne façon de représenter un dictionnaire dans le schéma XML. Le mieux que l'on puisse faire est de montrer qu'il y a une clé unique.

Vous pouvez toujours créer votre propre wrapper, par exemple Une façon de sérialiser les dictionnaires .

0 votes

Mes deux cas sont les services web et les fichiers de configuration. Vous êtes donc en train de dire que les concepteurs de .NET Framework ont été limités par une lacune dans la spécification XML Schema ? J'ai trouvé des choses en ligne, mais utiliser une classe intégrée demande beaucoup moins de travail que de décider si quelqu'un d'autre l'a fait correctement. Je vais jeter un coup d'œil à celle que vous avez suggérée.

0 votes

Les services web ASMX sont désormais considérés comme une technologie ancienne. Voir johnwsaundersiii.spaces.live.com/blog/ . Il existe une API complète pour les fichiers de configuration, qui n'utilise pas la sérialisation XML. Autre chose ?

0 votes

BTW, la "limitation" est une décision de conception. Comme vous le dites, il a été utilisé pour les services web - mais pas seulement pour sérialiser et désérialiser - c'est lui qui a produit les schémas qui font partie du WSDL. Tout cela fait partie d'un tout et doit fonctionner ensemble.

13voto

Joe Chung Points 6263

Ils en ont ajouté un dans .NET 3.0. Si vous le pouvez, ajoutez une référence à System.Runtime.Serialization et recherchez System.Xml.XmlDictionary, System.Xml.XmlDictionaryReader et System.Xml.XmlDictionaryWriter.

Je suis d'accord pour dire qu'il ne se trouve pas dans un endroit particulièrement facile à découvrir.

4 votes

Ces classes ne sont pas des dictionnaires sérialisables à usage général. Elles sont liées à la mise en œuvre de la sérialisation dans WCF.

0 votes

Je ne comprends pas le commentaire. Pourquoi ne s'agit-il pas de dictionnaires polyvalents sérialisables en xml ? Quelle partie de "System.Xml.XmlDictionary" ou de "System.Runtime.Serialization" indique qu'ils ne sont pas génériques ?

4voto

bang Points 1835

Créez-en un vous-même :-), la fonction de lecture seule est un bonus mais si vous avez besoin d'une clé autre qu'une chaîne de caractères, la classe doit être modifiée...

namespace MyNameSpace
{
    [XmlRoot("SerializableDictionary")]
    public class SerializableDictionary : Dictionary<String, Object>, IXmlSerializable
    {
        internal Boolean _ReadOnly = false;
        public Boolean ReadOnly
        {
            get
            {
                return this._ReadOnly;
            }

            set
            {
                this.CheckReadOnly();
                this._ReadOnly = value;
            }
        }

        public new Object this[String key]
        {
            get
            {
                Object value;

                return this.TryGetValue(key, out value) ? value : null;
            }

            set
            {
                this.CheckReadOnly();

                if(value != null)
                {
                    base[key] = value;
                }
                else
                {
                    this.Remove(key);
                }               
            }
        }

        internal void CheckReadOnly()
        {
            if(this._ReadOnly)
            {
                throw new Exception("Collection is read only");
            }
        }

        public new void Clear()
        {
            this.CheckReadOnly();

            base.Clear();
        }

        public new void Add(String key, Object value)
        {
            this.CheckReadOnly();

            base.Add(key, value);
        }

        public new void Remove(String key)
        {
            this.CheckReadOnly();

            base.Remove(key);
        }

        public XmlSchema GetSchema()
        {
            return null;
        }

        public void ReadXml(XmlReader reader)
        {
            Boolean wasEmpty = reader.IsEmptyElement;

            reader.Read();

            if(wasEmpty)
            {
                return;
            }

            while(reader.NodeType != XmlNodeType.EndElement)
            {
                if(reader.Name == "Item")
                {
                    String key = reader.GetAttribute("Key");
                    Type type = Type.GetType(reader.GetAttribute("TypeName"));

                    reader.Read();
                    if(type != null)
                    {
                        this.Add(key, new XmlSerializer(type).Deserialize(reader));
                    }
                    else
                    {
                        reader.Skip();
                    }
                    reader.ReadEndElement();

                    reader.MoveToContent();
                }
                else
                {
                    reader.ReadToFollowing("Item");
                }

            reader.ReadEndElement();
        }

        public void WriteXml(XmlWriter writer)
        {
            foreach(KeyValuePair<String, Object> item in this)
            {
                writer.WriteStartElement("Item");
                writer.WriteAttributeString("Key", item.Key);
                writer.WriteAttributeString("TypeName", item.Value.GetType().AssemblyQualifiedName);

                new XmlSerializer(item.Value.GetType()).Serialize(writer, item.Value);

                writer.WriteEndElement();
            }
        }

    }
}

0 votes

Il y avait un bogue dans ce code : s'il y avait des espaces dans le xml, la lecture pouvait entrer dans une boucle infinie. J'ai corrigé ce bug mais il peut y en avoir d'autres.

4voto

Utilisez le DataContractSerializer ! Voir l'exemple ci-dessous.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
using System.Xml;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            A a = new A();
            a.Value = 1;

            B b = new B();
            b.Value = "SomeValue";

            Dictionary<A, B> d = new Dictionary<A,B>();
            d.Add(a, b);
            DataContractSerializer dcs = new DataContractSerializer(typeof(Dictionary<A, B>));
            StringBuilder sb = new StringBuilder();
            using (XmlWriter xw = XmlWriter.Create(sb))
            {
                dcs.WriteObject(xw, d);
            }
            string xml = sb.ToString();
        }
    }

    public class A
    {
        public int Value
        {
            get;
            set;
        }
    }

    public class B
    {
        public string Value
        {
            get;
            set;
        }
    }
}

Le code ci-dessus produit le xml suivant :

<?xml version="1.0" encoding="utf-16"?>
<ArrayOfKeyValueOfABHtQdUIlS xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
    <KeyValueOfABHtQdUIlS>
        <Key xmlns:d3p1="http://schemas.datacontract.org/2004/07/ConsoleApplication1">
            <d3p1:Value>1</d3p1:Value>
        </Key>
        <Value xmlns:d3p1="http://schemas.datacontract.org/2004/07/ConsoleApplication1">
            <d3p1:Value>SomeValue</d3p1:Value>
        </Value>
    </KeyValueOfABHtQdUIlS>
</ArrayOfKeyValueOfABHtQdUIlS>

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X