Serializzare la classe contenente il membro del dizionario


144

Espandendosi sul mio problema precedente , ho deciso di (de) serializzare la mia classe di file di configurazione che ha funzionato alla grande.

Ora voglio per memorizzare un array associativo di lettere di unità per mappare (chiave è la lettera dell'unità, il valore è il percorso di rete) e hanno provato ad utilizzare Dictionary, HybridDictionarye Hashtableper questo, ma ho sempre trovato il seguente errore quando si chiama ConfigFile.Load()o ConfigFile.Save():

Si è verificato un errore nel tipo 'App.ConfigFile'. [snip] System.NotSupportedException: impossibile serializzare il membro App.Configfile.mappedDrives [snip]

Da quello che ho letto i dizionari e le tabelle hash possono essere serializzati, quindi cosa sto facendo di sbagliato?

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

Risposte:


77

Non è possibile serializzare una classe che implementa IDictionary. Dai un'occhiata a questo link .

D: Perché non riesco a serializzare gli hashtable?

A: XmlSerializer non può elaborare le classi che implementano l'interfaccia IDictionary. Ciò è dovuto in parte ai vincoli di pianificazione e in parte al fatto che una tabella hash non ha una controparte nel sistema di tipo XSD. L'unica soluzione è implementare una tabella hash personalizzata che non implementa l'interfaccia IDictionary.

Quindi penso che sia necessario creare la tua versione del dizionario per questo. Controlla questa altra domanda .


4
Mi chiedo solo che la DataContractSerializerclasse possa farlo. Solo l'output è un po 'brutto.
rekire il

186

C'è una soluzione nel Weblog di Paul Welter - Dizionario generico serializzabile XML

Per qualche motivo, il dizionario generico in .net 2.0 non è serializzabile XML. Il frammento di codice seguente è un dizionario generico serializzabile xml. Il dizionario è serializzabile implementando l'interfaccia 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
}

16
+1. Ehi, perché Stack Overflow non ha un pulsante per copiare il codice? Hmmm? perchè vale la pena copiare questo codice!
toddmo

1
+1 Risposta fantastica. Funziona anche con SortedList, appena cambiato "SerializableDictionary" in "SerializableSortedList" e "Dizionario <TKey, TValue>" in "SortedList <TKey, TValue>".
kdmurray,

1
+1 e un suggerimento. Quando un oggetto SerializableDictionary contiene più di un elemento, viene generata un'eccezione ... ReadXml () e WriteXml () devono essere modificati per spostare ReadStartElement ("item"); e WriteStartElement ("item"); e il ReadEndElement () e WriteEndElement () associati fuori dal ciclo while.
MNS,

1
Ciò significa quindi che nei quadri successivi l'IDictionary è serializzabile?
Thomas,

1
Questa implementazione funzionerà se il dizionario memorizza, per esempio, stringvalori, ma genererà una InvalidOperationExceptiondeserializzazione che menziona un elemento wrapper inatteso se si tenta di memorizzare oggetti personalizzati o array di stringhe in esso. (Vedi la mia domanda per un esempio dei problemi che ho dovuto affrontare.)
Christopher Kyle Horton,

57

Invece di usare XmlSerializerpuoi usare aSystem.Runtime.Serialization.DataContractSerializer . Questo può serializzare dizionari e interfacce senza sudore.

Ecco un link ad un esempio completo, http://theburningmonk.com/2010/05/net-tips-xml-serialize-or-deserialize-dictionary-in-csharp/


2
Migliore risposta, senza dubbio.
DWRoelands

D'accordo, questa è la risposta migliore. Pulito, semplice e ASCIUTTO (non ripetere te stesso).
Nolonar,

Il problema con DataContractSerializer è che serializza e deserializza in ordine alfabetico, quindi se provi a deserializzare qualcosa nell'ordine sbagliato fallirà silenziosamente con proprietà null nel tuo oggetto
superjugy

14

Creare un surrogato della serializzazione.

Esempio, hai una classe con proprietà pubblica di tipo Dizionario.

Per supportare la serializzazione Xml di questo tipo, creare una classe valore-chiave generica:

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

Aggiungi un attributo XmlIgnore alla tua proprietà originale:

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

Esporre una proprietà pubblica di tipo array, che contiene una matrice di istanze SerializableKeyValue, utilizzate per serializzare e deserializzare nella proprietà 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 );
            }
        }
    }

Mi piace perché disaccoppia la serializzazione dal membro del dizionario. Se avessi una classe molto usata a cui volevo aggiungere funzionalità di serializzazione, il wrapping del dizionario potrebbe causare un'interruzione con i tipi ereditati.
VoteCoffee

Un avvertimento per chiunque lo stia implementando: se provi a rendere la tua proprietà surrogata un Elenco (o qualsiasi altra raccolta ), il serializzatore XML non chiamerà il setter (invece chiama il getter e prova ad aggiungere all'elenco restituito, che ovviamente non è quello che volevi). Attenersi alle matrici per questo modello.
Fraxtil,

9

Dovresti esplorare Json.Net, abbastanza facile da usare e consente agli oggetti Json di essere deserializzati direttamente nel dizionario.

james_newtonking

esempio:

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

6

Dizionari e hashtabili non sono serializzabili con XmlSerializer. Pertanto non è possibile utilizzarli direttamente. Una soluzione alternativa sarebbe utilizzare l' XmlIgnoreattributo per nascondere quelle proprietà al serializzatore ed esporle tramite un elenco di coppie chiave-valore serializzabili.

PS: costruire un XmlSerializerè molto costoso, quindi memorizzalo sempre nella cache se c'è la possibilità di poterlo riutilizzare.


4

Volevo una classe SerializableDictionary che utilizzava attributi xml per chiave / valore, quindi ho adattato la classe di Paul Welter.

Questo produce xml come:

<Dictionary>
  <Item Key="Grass" Value="Green" />
  <Item Key="Snow" Value="White" />
  <Item Key="Sky" Value="Blue" />
</Dictionary>"

Codice:

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

namespace DataTypes {
    [XmlRoot("Dictionary")]
    public class SerializableDictionary<TKey, TValue>
        : Dictionary<TKey, TValue>, IXmlSerializable {
        #region IXmlSerializable Members
        public System.Xml.Schema.XmlSchema GetSchema() {
            return null;
        }

        public void ReadXml(XmlReader reader) {
            XDocument doc = null;
            using (XmlReader subtreeReader = reader.ReadSubtree()) {
                doc = XDocument.Load(subtreeReader);
            }
            XmlSerializer serializer = new XmlSerializer(typeof(SerializableKeyValuePair<TKey, TValue>));
            foreach (XElement item in doc.Descendants(XName.Get("Item"))) {
                using(XmlReader itemReader =  item.CreateReader()) {
                    var kvp = serializer.Deserialize(itemReader) as SerializableKeyValuePair<TKey, TValue>;
                    this.Add(kvp.Key, kvp.Value);
                }
            }
            reader.ReadEndElement();
        }

        public void WriteXml(System.Xml.XmlWriter writer) {
            XmlSerializer serializer = new XmlSerializer(typeof(SerializableKeyValuePair<TKey, TValue>));
            XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
            ns.Add("", "");
            foreach (TKey key in this.Keys) {
                TValue value = this[key];
                var kvp = new SerializableKeyValuePair<TKey, TValue>(key, value);
                serializer.Serialize(writer, kvp, ns);
            }
        }
        #endregion

        [XmlRoot("Item")]
        public class SerializableKeyValuePair<TKey, TValue> {
            [XmlAttribute("Key")]
            public TKey Key;

            [XmlAttribute("Value")]
            public TValue Value;

            /// <summary>
            /// Default constructor
            /// </summary>
            public SerializableKeyValuePair() { }
        public SerializableKeyValuePair (TKey key, TValue value) {
            Key = key;
            Value = value;
        }
    }
}
}

Test unitari:

using System.IO;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace DataTypes {
    [TestClass]
    public class SerializableDictionaryTests {
        [TestMethod]
        public void TestStringStringDict() {
            var dict = new SerializableDictionary<string, string>();
            dict.Add("Grass", "Green");
            dict.Add("Snow", "White");
            dict.Add("Sky", "Blue");
            dict.Add("Tomato", "Red");
            dict.Add("Coal", "Black");
            dict.Add("Mud", "Brown");

            var serializer = new System.Xml.Serialization.XmlSerializer(dict.GetType());
            using (var stream = new MemoryStream()) {
                // Load memory stream with this objects xml representation
                XmlWriter xmlWriter = null;
                try {
                    xmlWriter = XmlWriter.Create(stream);
                    serializer.Serialize(xmlWriter, dict);
                } finally {
                    xmlWriter.Close();
                }

                // Rewind
                stream.Seek(0, SeekOrigin.Begin);

                XDocument doc = XDocument.Load(stream);
                Assert.AreEqual("Dictionary", doc.Root.Name);
                Assert.AreEqual(dict.Count, doc.Root.Descendants().Count());

                // Rewind
                stream.Seek(0, SeekOrigin.Begin);
                var outDict = serializer.Deserialize(stream) as SerializableDictionary<string, string>;
                Assert.AreEqual(dict["Grass"], outDict["Grass"]);
                Assert.AreEqual(dict["Snow"], outDict["Snow"]);
                Assert.AreEqual(dict["Sky"], outDict["Sky"]);
            }
        }

        [TestMethod]
        public void TestIntIntDict() {
            var dict = new SerializableDictionary<int, int>();
            dict.Add(4, 7);
            dict.Add(5, 9);
            dict.Add(7, 8);

            var serializer = new System.Xml.Serialization.XmlSerializer(dict.GetType());
            using (var stream = new MemoryStream()) {
                // Load memory stream with this objects xml representation
                XmlWriter xmlWriter = null;
                try {
                    xmlWriter = XmlWriter.Create(stream);
                    serializer.Serialize(xmlWriter, dict);
                } finally {
                    xmlWriter.Close();
                }

                // Rewind
                stream.Seek(0, SeekOrigin.Begin);

                XDocument doc = XDocument.Load(stream);
                Assert.AreEqual("Dictionary", doc.Root.Name);
                Assert.AreEqual(3, doc.Root.Descendants().Count());

                // Rewind
                stream.Seek(0, SeekOrigin.Begin);
                var outDict = serializer.Deserialize(stream) as SerializableDictionary<int, int>;
                Assert.AreEqual(dict[4], outDict[4]);
                Assert.AreEqual(dict[5], outDict[5]);
                Assert.AreEqual(dict[7], outDict[7]);
            }
        }
    }
}

1
Sembra buono ma fallisce con un dizionario vuoto. È necessario il test reader.IsEmptyElement nel metodo ReadXML.
AnthonyVO,

2

la classe Dictionary implementa ISerializable. La definizione di Class Dictionary fornita di seguito.

[DebuggerTypeProxy(typeof(Mscorlib_DictionaryDebugView<,>))]
[DebuggerDisplay("Count = {Count}")]
[Serializable]
[System.Runtime.InteropServices.ComVisible(false)]
public class Dictionary<TKey,TValue>: IDictionary<TKey,TValue>, IDictionary, IReadOnlyDictionary<TKey, TValue>, ISerializable, IDeserializationCallback  

Non penso che sia questo il problema. fare riferimento al seguente link, che dice che se si dispone di qualsiasi altro tipo di dati che non è serializzabile, il dizionario non verrà serializzato. http://forums.asp.net/t/1734187.aspx?Is+Dictionary+serializable+


Questo è vero nelle ultime versioni, ma in .NET 2, Dictionary non è serializzabile, anche oggi. L'ho confermato proprio oggi con un progetto destinato a .NET 3.5, ed è così che ho trovato questo thread.
Bruce,

2

È possibile utilizzare ExtendedXmlSerializer . Se hai una lezione:

public class ConfigFile
{
    public String guiPath { get; set; }
    public string configPath { get; set; }
    public Dictionary<string, string> mappedDrives {get;set;} 

    public ConfigFile()
    {
        mappedDrives = new Dictionary<string, string>();
    }
}

e creare istanza di questa classe:

ConfigFile config = new ConfigFile();
config.guiPath = "guiPath";
config.configPath = "configPath";
config.mappedDrives.Add("Mouse", "Logitech MX Master");
config.mappedDrives.Add("keyboard", "Microsoft Natural Ergonomic Keyboard 4000");

È possibile serializzare questo oggetto utilizzando ExtendedXmlSerializer:

ExtendedXmlSerializer serializer = new ExtendedXmlSerializer();
var xml = serializer.Serialize(config);

L'output xml sarà simile a:

<?xml version="1.0" encoding="utf-8"?>
<ConfigFile type="Program+ConfigFile">
    <guiPath>guiPath</guiPath>
    <configPath>configPath</configPath>
    <mappedDrives>
        <Item>
            <Key>Mouse</Key>
            <Value>Logitech MX Master</Value>
        </Item>
        <Item>
            <Key>keyboard</Key>
            <Value>Microsoft Natural Ergonomic Keyboard 4000</Value>
        </Item>
    </mappedDrives>
</ConfigFile>

È possibile installare ExtendedXmlSerializer dal nuget o eseguire il comando seguente:

Install-Package ExtendedXmlSerializer

Ecco un esempio online


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.