Serialize Class mit Dictionary-Mitglied

144

Als Erweiterung meines früheren Problems habe ich beschlossen, meine Konfigurationsdateiklasse (de) zu serialisieren, was hervorragend funktioniert hat.

Ich möchte jetzt ein assoziatives Array von Laufwerksbuchstaben speichern , zu kartieren (Schlüssel ist der Laufwerksbuchstabe, den Wert der Netzwerkpfad) und haben versucht , mit Dictionary, HybridDictionaryund Hashtabledafür , aber ich immer folgende Fehlermeldung erhalten , wenn Sie anrufen ConfigFile.Load()oder ConfigFile.Save():

Es ist ein Fehler aufgetreten, der den Typ 'App.ConfigFile' widerspiegelt. [snip] System.NotSupportedException: Mitglied App.Configfile.mappedDrives kann nicht serialisiert werden [snip]

Nach dem, was ich gelesen habe, können Wörterbücher und HashTables serialisiert werden. Was mache ich also falsch?

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

Antworten:

77

Sie können eine Klasse, die IDictionary implementiert, nicht serialisieren. Schauen Sie sich diesen Link an .

F: Warum kann ich Hashtabellen nicht serialisieren?

A: Der XmlSerializer kann keine Klassen verarbeiten, die die IDictionary-Schnittstelle implementieren. Dies war teilweise auf Zeitplanbeschränkungen und teilweise auf die Tatsache zurückzuführen, dass eine Hashtabelle kein Gegenstück im XSD-Typsystem hat. Die einzige Lösung besteht darin, eine benutzerdefinierte Hashtabelle zu implementieren, die die IDictionary-Schnittstelle nicht implementiert.

Ich denke, Sie müssen dafür Ihre eigene Version des Wörterbuchs erstellen. Überprüfen Sie diese andere Frage .

bruno conde
quelle
4
Ich frage mich DataContractSerializernur, ob die Klasse das kann. Nur die Ausgabe ist etwas hässlich.
rekire
186

Im Weblog von Paul Welter gibt es eine Lösung - XML ​​Serializable Generic Dictionary

Aus irgendeinem Grund ist das generische Wörterbuch in .net 2.0 nicht XML-serialisierbar. Das folgende Code-Snippet ist ein XML-serialisierbares generisches Wörterbuch. Das Wörterbuch kann durch Implementieren der IXmlSerializable-Schnittstelle serialisiert werden.

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
}
Osman Pirci
quelle
16
+1. Hey, warum hat Stack Overflow keine Schaltfläche zum Kopieren von Codes? Hmmm? Denn dieser Code ist es wert, kopiert zu werden!
Toddmo
1
+1 Fantastische Antwort. Funktioniert auch für SortedList, hat gerade das "SerializableDictionary" in "SerializableSortedList" und das "Dictionary <TKey, TValue>" in "SortedList <TKey, TValue>" geändert.
Kdmurray
1
+1 und ein Vorschlag. Wenn ein SerializableDictionary-Objekt mehr als ein Element enthält, wird eine Ausnahme ausgelöst ... ReadXml () und WriteXml () sollten geändert werden, um ReadStartElement ("item") zu verschieben. und WriteStartElement ("item"); und die zugehörigen ReadEndElement () und WriteEndElement () aus der while-Schleife.
MNS
1
Bedeutet das dann, dass das IDictionary in späteren Frameworks serialisierbar ist?
Thomas
1
Diese Implementierung funktioniert, wenn das Wörterbuch beispielsweise stringWerte speichert, löst jedoch eine InvalidOperationExceptionDeserialisierung aus, in der ein unerwartetes Wrapper-Element erwähnt wird, wenn Sie versuchen, benutzerdefinierte Objekte oder String-Arrays darin zu speichern. (Siehe meine Frage für ein Beispiel der Probleme, mit denen ich konfrontiert war.)
Christopher Kyle Horton
57

Anstelle von können XmlSerializerSie auch a verwenden System.Runtime.Serialization.DataContractSerializer. Dies kann Wörterbücher serialisieren und Schnittstellen ohne Schweiß.

Hier ist ein Link zu einem vollständigen Beispiel: http://theburningmonk.com/2010/05/net-tips-xml-serialize-or-deserialize-dictionary-in-csharp/

Despertar
quelle
2
Beste Antwort, zweifellos.
DWRoelands
Einverstanden, das ist die beste Antwort. Sauber, einfach und trocken (wiederholen Sie sich nicht).
Nolonar
Problem mit DataContractSerializer ist, dass es in alphabetischer Reihenfolge serialisiert und deserialisiert. Wenn Sie also versuchen, etwas in der falschen Reihenfolge zu deserialisieren,
schlägt
14

Erstellen Sie einen Serialisierungsersatz.

Beispiel: Sie haben eine Klasse mit einer öffentlichen Eigenschaft vom Typ Dictionary.

Erstellen Sie eine generische Schlüsselwertklasse, um die XML-Serialisierung dieses Typs zu unterstützen:

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

Fügen Sie Ihrer ursprünglichen Eigenschaft ein XmlIgnore-Attribut hinzu:

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

Stellen Sie eine öffentliche Eigenschaft vom Array-Typ bereit, die ein Array von SerializableKeyValue-Instanzen enthält, die zum Serialisieren und Deserialisieren in die SearchCategories-Eigenschaft verwendet werden:

    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 );
            }
        }
    }
user2921681
quelle
Ich mag das, weil es die Serialisierung vom Wörterbuchmitglied entkoppelt. Wenn ich eine häufig verwendete Klasse hätte, der ich Serialisierungsfunktionen hinzufügen wollte, könnte das Umschließen des Wörterbuchs zu einer Unterbrechung der geerbten Typen führen.
VoteCoffee
Ein Wort der Vorsicht für alle, die dies implementieren: Wenn Sie versuchen, Ihre Ersatz-Eigenschaft zu einer Liste (oder einer anderen Sammlung ) zu machen, ruft der XML-Serializer den Setter nicht auf (stattdessen ruft er den Getter auf und versucht, der zurückgegebenen Liste hinzuzufügen). was offensichtlich nicht das ist, was du wolltest). Halten Sie sich für dieses Muster an Arrays.
Fraxtil
9

Sie sollten Json.Net erkunden, das recht einfach zu verwenden ist und die direkte Deserialisierung von Json-Objekten im Wörterbuch ermöglicht.

james_newtonking

Beispiel:

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
Jean-Philippe Kies
quelle
6

Wörterbücher und Hashtabellen können nicht mit serialisiert werden XmlSerializer. Daher können Sie sie nicht direkt verwenden. Eine Problemumgehung wäre die Verwendung vonXmlIgnore Attribut zu verwenden, um diese Eigenschaften vor dem Serializer auszublenden und über eine Liste serialisierbarer Schlüssel-Wert-Paare verfügbar zu machen.

PS: Das Erstellen eines XmlSerializerist sehr teuer, also zwischenspeichern Sie es immer, wenn die Möglichkeit besteht, es wiederzuverwenden.

David Schmitt
quelle
4

Ich wollte eine SerializableDictionary-Klasse, die XML-Attribute für Schlüssel / Wert verwendet, also habe ich die Klasse von Paul Welter angepasst.

Dies erzeugt XML wie:

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

Code:

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

Unit Tests:

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]);
            }
        }
    }
}
Keyo
quelle
1
Sieht gut aus, schlägt aber mit einem leeren Wörterbuch fehl. Sie benötigen den reader.IsEmptyElement-Test in der ReadXML-Methode.
AnthonyVO
2

Die Dictionary-Klasse implementiert ISerializable. Die unten angegebene Definition des Klassenwörterbuchs.

[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  

Ich denke nicht, dass das das Problem ist. Verweisen Sie auf den folgenden Link, der besagt, dass das Wörterbuch nicht serialisiert wird, wenn Sie einen anderen Datentyp haben, der nicht serialisierbar ist. http://forums.asp.net/t/1734187.aspx?Is+Dictionary+serializable+

Saikrishna
quelle
Dies gilt in den neuesten Versionen, aber in .NET 2 ist Dictionary auch heute noch nicht serialisierbar. Ich habe es erst heute mit einem Projekt bestätigt, das auf .NET 3.5 abzielt. So habe ich diesen Thread gefunden.
Bruce
2

Sie können ExtendedXmlSerializer verwenden . Wenn Sie eine Klasse haben:

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

und erstellen Sie eine Instanz dieser Klasse:

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");

Sie können dieses Objekt mit ExtendedXmlSerializer serialisieren:

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

Die Ausgabe-XML sieht folgendermaßen aus:

<?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>

Sie können ExtendedXmlSerializer von Nuget aus installieren oder den folgenden Befehl ausführen:

Install-Package ExtendedXmlSerializer

Hier ist ein Online-Beispiel

Wojtpl2
quelle