Serialize Class yang berisi anggota Kamus

144

Memperluas masalah saya sebelumnya , saya telah memutuskan (de) membuat serial file kelas config saya yang bekerja sangat baik.

Sekarang saya ingin menyimpan array asosiatif huruf drive untuk memetakan (key adalah huruf drive, nilai adalah jalur jaringan) dan telah mencoba menggunakan Dictionary, HybridDictionarydan Hashtableuntuk ini, tetapi saya selalu mendapatkan error berikut saat memanggil ConfigFile.Load()atau ConfigFile.Save():

Ada kesalahan yang mencerminkan tipe 'App.ConfigFile'. [snip] System.NotSupportedException: Tidak dapat membuat serialisasi anggota App.Configfile.mappedDrives [snip]

Dari apa yang saya baca Kamus dan HashTable dapat diserialisasi, jadi apa yang saya lakukan salah?

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

Jawaban:

77

Anda tidak dapat membuat cerita bersambung kelas yang mengimplementasikan IDictionary. Lihat tautan ini .

T: Mengapa saya tidak bisa membuat serial hashtables?

A: XmlSerializer tidak dapat memproses kelas yang mengimplementasikan antarmuka IDictionary. Ini sebagian karena kendala jadwal dan sebagian karena fakta bahwa hashtable tidak memiliki mitra dalam sistem tipe XSD. Satu-satunya solusi adalah menerapkan hashtable khusus yang tidak mengimplementasikan antarmuka IDictionary.

Jadi saya pikir Anda perlu membuat versi kamus Anda sendiri untuk ini. Lihat pertanyaan lain ini .

bruno conde
sumber
4
Hanya ingin tahu DataContractSerializerkelas bisa melakukannya. Hanya hasilnya agak jelek.
rekire
186

Ada solusi di Weblog Paul Welter - Kamus XML Serializable Generik

Untuk beberapa alasan, Kamus generik di .net 2.0 bukan XML yang dapat diserialisasi. Cuplikan kode berikut adalah kamus umum xml berseri serial. Kamus ini serialzable dengan mengimplementasikan antarmuka 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
}
osman pirci
sumber
16
+1. Hei, mengapa Stack Overflow tidak memiliki tombol kode salin? Hmmm? karena kode ini layak untuk disalin!
toddmo
1
+1 Jawaban yang fantastis. Juga berfungsi untuk SortedList, baru saja mengubah "SerializableDictionary" menjadi "SerializableSortedList" dan "Kamus <TKey, TValue>" menjadi "SortedList <TKey, TValue>".
kdmurray
1
+1 dan satu saran. Ketika objek SerializableDictionary memegang lebih dari satu elemen, pengecualian dilempar ... ReadXml () dan WriteXml () harus dimodifikasi untuk memindahkan ReadStartElement ("item"); dan WriteStartElement ("item"); dan ReadEndElement () dan WriteEndElement () yang terkait dari loop sementara.
MNS
1
Apakah itu berarti bahwa dalam kerangka kerja selanjutnya, IDictionary dapat serial?
Thomas
1
Implementasi ini akan berfungsi jika kamus menyimpan, mengatakan, stringnilai-nilai, tetapi akan menampilkan InvalidOperationExceptiondeserialisasi yang menyebutkan elemen pembungkus yang tidak terduga jika Anda mencoba menyimpan objek khusus atau array string di dalamnya. (Lihat pertanyaan saya untuk contoh masalah yang saya hadapi dengan ini.)
Christopher Kyle Horton
57

Alih-alih menggunakan XmlSerializerAnda dapat menggunakan a System.Runtime.Serialization.DataContractSerializer. Ini dapat membuat serial kamus dan antarmuka tidak berkeringat.

Berikut ini tautan ke contoh lengkap, http://theburningmonk.com/2010/05/net-tips-xml-serialize-or-deserialize-dictionary-in-csharp/

Despertar
sumber
2
Jawaban terbaik, tangan-ke-bawah.
DWRoelands
Setuju, ini adalah jawaban terbaik. Bersih, sederhana, dan KERING (Jangan Ulangi Diri Sendiri).
Nolonar
Masalah dengan DataContractSerializer adalah bahwa ia membuat cerita bersambung dan deserialisasi dalam urutan abjad sehingga jika Anda mencoba untuk men-deserialisasi sesuatu dengan urutan yang salah, ia akan gagal secara diam-diam dengan properti nol di objek Anda
superjugy
14

Buat pengganti serialisasi.

Contoh, Anda memiliki kelas dengan properti publik dari Kamus jenis.

Untuk mendukung serialisasi Xml jenis ini, buat kelas nilai kunci umum:

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

Tambahkan atribut XmlIgnore ke properti asli Anda:

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

Mengekspos properti publik dari tipe array, yang menyimpan sebuah array dari instance SerializableKeyValue, yang digunakan untuk membuat serialisasi dan membatalkan deserialisasi ke dalam properti 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 );
            }
        }
    }
pengguna2921681
sumber
Saya suka ini karena memisahkan serialisasi dari anggota kamus. Jika saya memiliki kelas yang banyak digunakan yang saya ingin menambahkan kemampuan serialisasi, maka membungkus kamus dapat menyebabkan jeda dengan tipe yang diwarisi.
VoteCoffee
Kata hati-hati untuk siapa pun yang mengimplementasikan ini: jika Anda mencoba menjadikan properti pengganti Anda Daftar (atau koleksi lainnya ), serializer XML tidak akan memanggil setter (sebaliknya ia memanggil pengambil dan mencoba menambah daftar yang dikembalikan, yang jelas bukan yang Anda inginkan). Tetap berpegang pada array untuk pola ini.
Fraxtil
9

Anda harus menjelajahi Json.Net, cukup mudah digunakan dan memungkinkan objek Json untuk di-deserialisasi dalam Kamus secara langsung.

james_newtonking

contoh:

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 Gravel
sumber
6

Kamus dan Hashtable tidak bersambung dengan XmlSerializer. Karena itu Anda tidak dapat menggunakannya secara langsung. Solusinya adalah dengan menggunakan XmlIgnoreatribut untuk menyembunyikan properti-properti dari serializer dan memaparkannya melalui daftar pasangan kunci-nilai serial.

PS: membangun sebuah XmlSerializersangat mahal, jadi selalu cache itu jika ada kesempatan untuk dapat menggunakannya kembali.

David Schmitt
sumber
4

Saya ingin kelas SerializableDictionary yang menggunakan atribut xml untuk kunci / nilai jadi saya telah mengadaptasi kelas Paul Welter.

Ini menghasilkan xml seperti:

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

Kode:

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

Tes Unit:

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
sumber
1
Terlihat bagus tetapi gagal dengan Kamus kosong. Anda memerlukan tes reader.IsEmptyElement dalam metode ReadXML.
AnthonyVO
2

kelas Kamus mengimplementasikan ISerializable. Definisi Kamus Kelas diberikan di bawah ini.

[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  

Saya tidak berpikir itu masalahnya. lihat tautan di bawah ini, yang mengatakan bahwa jika Anda memiliki tipe data lain yang tidak bisa serial maka Kamus tidak akan diserialisasi. http://forums.asp.net/t/1734187.aspx?Is+Dictionary+serializable+

Saikrishna
sumber
Itu benar dalam versi terbaru, tetapi dalam. NET 2, Kamus tidak serializable, bahkan hari ini. Saya mengkonfirmasi hari ini hanya dengan proyek yang menargetkan .NET 3.5, yang merupakan cara saya menemukan utas ini.
Bruce
2

Anda dapat menggunakan ExtendedXmlSerializer . Jika Anda memiliki kelas:

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

dan buat instance dari kelas ini:

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

Anda dapat membuat serial objek ini menggunakan ExtendedXmlSerializer:

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

Output xml akan terlihat seperti:

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

Anda dapat menginstal ExtendedXmlSerializer dari nuget atau menjalankan perintah berikut:

Install-Package ExtendedXmlSerializer

Ini adalah contoh online

Wojtpl2
sumber