158 votes

Serialize Class contenant un membre Dictionary

En développant mon problème antérieur J'ai décidé de (dé)sérialiser ma classe de fichier de configuration, ce qui a bien fonctionné.

Je veux maintenant stocker un tableau associatif de lettres de lecteur à mettre en correspondance (la clé est la lettre de lecteur, la valeur est le chemin d'accès au réseau) et j'ai essayé d'utiliser l'option Dictionary , HybridDictionary et Hashtable pour cela, mais j'obtiens toujours l'erreur suivante lorsque j'appelle ConfigFile.Load() ou ConfigFile.Save() :

Il y a eu une erreur reflétant le type 'App.ConfigFile'. [snip] System.NotSupportedException : Impossible de sérialiser le membre App.Configfile.mappedDrives [snip]

D'après ce que j'ai lu, les dictionnaires et les tables de hachage peuvent être sérialisés, alors qu'est-ce que je fais de travers ?

[XmlRoot(ElementName="Config")]
public class ConfigFile
{
    public String guiPath { get; set; }
    public string configPath { get; set; }
    public Dictionary<string, string> mappedDrives = new Dictionary<string, string>();

    public Boolean Save(String filename)
    {
        using(var filestream = File.Open(filename, FileMode.OpenOrCreate,FileAccess.ReadWrite))
        {
            try
            {
                var serializer = new XmlSerializer(typeof(ConfigFile));
                serializer.Serialize(filestream, this);
                return true;
            } catch(Exception e) {
                MessageBox.Show(e.Message);
                return false;
            }
        }
    }

    public void addDrive(string drvLetter, string path)
    {
        this.mappedDrives.Add(drvLetter, path);
    }

    public static ConfigFile Load(string filename)
    {
        using (var filestream = File.Open(filename, FileMode.Open, FileAccess.Read))
        {
            try
            {
                var serializer = new XmlSerializer(typeof(ConfigFile));
                return (ConfigFile)serializer.Deserialize(filestream);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message + ex.ToString());
                return new ConfigFile();
            }
        }
    }
}

194voto

osman pirci Points 531

Il existe une solution à Paul Welter's Weblog - Dictionnaire générique sérialisable XML

Pour une raison quelconque, le dictionnaire générique de .net 2.0 n'est pas sérialisable en XML. L'extrait de code suivant est un dictionnaire générique sérialisable en XML. Le dictionnaire est sérialisable en implémentant l'interface IXmlSerializable.

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

[XmlRoot("dictionary")]
public class SerializableDictionary<TKey, TValue>
    : Dictionary<TKey, TValue>, IXmlSerializable
{
    public SerializableDictionary() { }
    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) { }

    #region IXmlSerializable Members
    public System.Xml.Schema.XmlSchema GetSchema()
    {
        return null;
    }

    public void ReadXml(System.Xml.XmlReader reader)
    {
        XmlSerializer keySerializer = new XmlSerializer(typeof(TKey));
        XmlSerializer valueSerializer = new 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();

            this.Add(key, value);

            reader.ReadEndElement();
            reader.MoveToContent();
        }
        reader.ReadEndElement();
    }

    public void WriteXml(System.Xml.XmlWriter writer)
    {
        XmlSerializer keySerializer = new XmlSerializer(typeof(TKey));
        XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue));

        foreach (TKey key in this.Keys)
        {
            writer.WriteStartElement("item");

            writer.WriteStartElement("key");
            keySerializer.Serialize(writer, key);
            writer.WriteEndElement();

            writer.WriteStartElement("value");
            TValue value = this[key];
            valueSerializer.Serialize(writer, value);
            writer.WriteEndElement();

            writer.WriteEndElement();
        }
    }
    #endregion
}

1 votes

+1 Réponse fantastique. Fonctionne également pour SortedList, il suffit de changer le "SerializableDictionary" en "SerializableSortedList" et le "Dictionary<TKey, TValue>" en "SortedList<TKey, TValue>".

0 votes

Fonctionne très bien. La réponse acceptée ci-dessus cite MS disant "La seule solution est d'implémenter un hashtable personnalisé qui n'implémente pas l'interface IDictionary". Mais ce n'est pas vrai, n'est-ce pas ? La solution ci-dessus hérite de Dictionary (et donc implémente iDictionary) mais implémente aussi IXMLSerializable. Ou ai-je manqué quelque chose ?

0 votes

J'ai ajouté une adaptation de cette classe dans ma réponse qui utilise des attributs pour les clés/valeurs au lieu d'éléments.

78voto

bruno conde Points 28120

Vous ne pouvez pas sérialiser une classe qui implémente IDictionary. Vérifiez ceci lien .

Q : Pourquoi ne puis-je pas sérialiser les hashtables ?

R : Le XmlSerializer ne peut pas traiter les classes les classes implémentant l'interface IDictionary . Ceci est dû en partie à des contraintes de calendrier et en partie à cause du le fait qu'une table de hachage n'a pas n'a pas d'équivalent dans le système de dans le système de types XSD. La seule solution consiste à d'implémenter une table de hachage personnalisée qui pas l'interface IDictionary qui n'implémente pas l'interface IDictionary.

Je pense donc que vous devez créer votre propre version du dictionnaire pour cela. Vérifiez ceci autre question .

4 votes

Je me demandais juste DataContractSerializer La classe peut le faire. La sortie est juste un peu moche.

60voto

Despertar Points 5365

Au lieu d'utiliser XmlSerializer vous pouvez utiliser un System.Runtime.Serialization.DataContractSerializer . Cela permet de sérialiser les dictionnaires et les interfaces sans problème.

Voici un lien vers un exemple complet, http://theburningmonk.com/2010/05/net-tips-xml-serialize-or-deserialize-dictionary-in-csharp/

0 votes

Le problème avec DataContractSerializer est qu'il sérialise et désérialise dans l'ordre alphabétique, donc si vous essayez de désérialiser quelque chose dans le mauvais ordre, il échouera silencieusement avec des propriétés nulles dans votre objet.

18voto

user2921681 Points 51

Créer un substitut de sérialisation.

Exemple, vous avez une classe avec une propriété publique de type Dictionnaire.

Pour prendre en charge la sérialisation Xml de ce type, créez une classe générique clé-valeur :

public class SerializeableKeyValue<T1,T2>
{
    public T1 Key { get; set; }
    public T2 Value { get; set; }
}

Ajoutez un attribut XmlIgnore à votre propriété originale :

    [XmlIgnore]
    public Dictionary<int, string> SearchCategories { get; set; }

Expose une propriété publique de type tableau, qui contient un tableau d'instances SerializableKeyValue, qui sont utilisées pour sérialiser et désérialiser dans la propriété SearchCategories :

    public SerializeableKeyValue<int, string>[] SearchCategoriesSerializable
    {
        get
        {
            var list = new List<SerializeableKeyValue<int, string>>();
            if (SearchCategories != null)
            {
                list.AddRange(SearchCategories.Keys.Select(key => new SerializeableKeyValue<int, string>() {Key = key, Value = SearchCategories[key]}));
            }
            return list.ToArray();
        }
        set
        {
            SearchCategories = new Dictionary<int, string>();
            foreach (var item in value)
            {
                SearchCategories.Add( item.Key, item.Value );
            }
        }
    }

0 votes

J'aime cette méthode parce qu'elle découple la sérialisation du membre du dictionnaire. Si je disposais d'une classe très utilisée à laquelle je voulais ajouter des fonctionnalités de sérialisation, l'encapsulation du dictionnaire pourrait entraîner une rupture avec les types hérités.

2 votes

Une mise en garde pour tous ceux qui mettent en œuvre ce système : si vous essayez de faire de votre propriété de substitution une liste (ou tout autre type de propriété de substitution), vous risquez d'avoir à faire face à des problèmes de sécurité. collection ), le sérialiseur XML n'appellera pas le setter (au lieu de cela, il appelle le getter et essaie d'ajouter à la liste retournée, ce qui n'est évidemment pas ce que vous vouliez). Utilisez des tableaux pour ce modèle.

9voto

Vous devriez explorer Json.Net, assez facile à utiliser et qui permet de désérialiser les objets Json directement dans Dictionary.

james_newtonking

exemple :

string json = @"{""key1"":""value1"",""key2"":""value2""}";
Dictionary<string, string> values = JsonConvert.DeserializeObject<Dictionary<string, string>>(json); 
Console.WriteLine(values.Count);
// 2
Console.WriteLine(values["key1"]);
// value1

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