Серіалізувати клас, що містить член словника


144

Розширюючи свою попередню проблему , я вирішив (де) серіалізувати свій клас конфігураційних файлів, який чудово працював.

Тепер я хочу , щоб зберігати асоціативний масив букв на карту (ключ буква диска, значення мережевого шлях) і спробував використовувати Dictionary, HybridDictionaryі Hashtableдля цього , але я завжди отримую наступне повідомлення про помилку при виклику ConfigFile.Load()або ConfigFile.Save():

Виникла помилка, що відображає тип "App.ConfigFile". [snip] System.NotSupportedException: Не вдається серіалізувати член App.Configfile.mappedDrives [snip]

З того, що я читав, Словники та HashTables можна серіалізувати, і що я роблю неправильно?

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

Відповіді:


77

Ви не можете серіалізувати клас, який реалізує IDictionary. Перевірте це посилання .

Питання: Чому я не можу серіалізувати хештелі?

A: XmlSerializer не може обробляти класи, що реалізують інтерфейс IDictionary. Частково це було пов'язано з обмеженнями у графіку, а частково через те, що хештел не має аналога в системі типу XSD. Єдине рішення - реалізувати користувацький хештель, який не реалізує інтерфейс IDictionary.

Тому я думаю, що для цього вам потрібно створити власну версію Словника. Перевірте це інше питання .


4
Просто цікаво, що DataContractSerializerклас може це зробити. Просто вихід трохи некрасивий.
rekire

186

У веб-журналі Пола Вельтера є рішення - XML ​​Serializable Generic Dictionary

З певних причин загальний словник у .net 2.0 не є XML-серіалізаційним. Наступний фрагмент коду - це загальнодоступний загальнодоступний словник xml. Словник можна серіалізувати, реалізуючи інтерфейс 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. Гей, чому у Stack Overflow немає кнопки коду копіювання? Хммм? Тому що цей код варто скопіювати!
toddmo

1
+1 Фантастична відповідь. Також працює для SortedList, щойно змінив "SerializableDictionary" на "SerializableSortedList", а "Словник <TKey, TValue>" на "SortedList <TKey, TValue>".
kdmurray

1
+1 і одна пропозиція. Коли об’єкт SerializableDictionary містить більше ніж один елемент, викид буде викинуто ... ReadXml () та WriteXml () слід змінити для переміщення ReadStartElement ("елемент"); і WriteStartElement ("елемент"); і пов'язані з нею цикл "час" ReadEndElement () та WriteEndElement ().
MNS

1
Чи означає це тоді, що в пізніших рамках IDRL серіалізується?
Томас

1
Ця реалізація спрацює, якщо словник зберігає, скажімо, stringзначення, але додасть InvalidOperationExceptionдесеріалізацію, згадавши про несподіваний елемент обгортки, якщо ви спробуєте зберегти в ній користувацькі об'єкти або рядкові масиви. (Див. Моє запитання для прикладу питань, з якими я стикався.)
Крістофер Кайл Хортон

57

Замість використання XmlSerializerви можете використовувати a System.Runtime.Serialization.DataContractSerializer. Це може серіалізувати словники та інтерфейси без потужності.

Ось посилання на повний приклад, http://theburningmonk.com/2010/05/net-tips-xml-serialize-or-deserialize-dictionary-in-csharp/


2
Найкраща відповідь, руки вниз.
DWRoelands

Погодився, це найкраща відповідь. Чисто, просто та ДУХО (не повторюйте себе).
Нолонар

Проблема з DataContractSerializer полягає в тому, що він серіалізує та десеріалізує в алфавітному порядку, тож якщо ви спробуєте десаріалізувати щось у неправильному порядку, воно мовчить з нульовими властивостями у вашому об’єкті
superjugy

14

Створіть сурогат серіалізації.

Наприклад, у вас є клас із загальнодоступною властивістю типу словник.

Для підтримки серіалізації Xml цього типу створіть загальний клас ключових значень:

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

Додайте атрибут XmlIgnore до початкової власності:

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

Розкрийте загальнодоступну властивість типу масиву, яка містить масив екземплярів SerializableKeyValue, які використовуються для серіалізації та деріаріалізації у властивості 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 );
            }
        }
    }

Мені це подобається, тому що він відриває серіалізацію від члена словника. Якщо б у мене використовувався клас, який широко використовується, до якого я хотів додати можливості серіалізації, то загортання словника може спричинити розрив із успадкованими типами.
VoteCoffee

Слова застереження для тих, хто реалізує це: якщо ви спробуєте зробити свій сурогатний ресурс списком (або будь-якою іншою колекцією ), серійний інструмент XML не зателефонує сеттеру (натомість він викликає геттера і намагається додати до повернутого списку, що, очевидно, не те, чого ви хотіли). Дотримуйтесь масивів для цього шаблону.
Fraxtil

9

Ви повинні вивчити Json.Net, досить простий у використанні та дозволяє десаріалізувати об’єкти Json безпосередньо у словнику.

james_newtonking

приклад:

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

Словники та хешшлети не піддаються серіалізації XmlSerializer. Тому ви не можете використовувати їх безпосередньо. Приблизним рішенням буде використанняXmlIgnore атрибутом, щоб приховати ці властивості від серіалізатора та викрити їх за допомогою списку серіалізаційних пар ключ-значення.

PS: побудувати an XmlSerializerдуже дорого, тому завжди кешуйте його, якщо є можливість повторного використання.


4

Я хотів клас SerializableDictionary, який використовував атрибути xml для ключа / значення, тому я адаптував клас Пола Вельтера.

Виходить xml, наприклад:

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

Код:

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

Тестові одиниці:

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
Виглядає добре, але не вдається з порожнім словником. Вам потрібен тест читача.IsEmptyElement у методі ReadXML.
АнтонійВО

2

клас Словник реалізує ISerializable. Визначення Словника класів наведено нижче.

[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  

Я не думаю, що це проблема. Посилайтеся на посилання нижче, де сказано, що якщо у вас є будь-який інший тип даних, який не піддається серіалізації, словник не буде серіалізований. http://forums.asp.net/t/1734187.aspx?Is+Dictionary+serializable+


Це вірно в останніх версіях, але в .NET 2, Словник не є серіалізаційним навіть сьогодні. Я підтвердив це саме сьогодні, орієнтуючись на проект .NET 3.5, саме таким чином я знайшов цю тему.
Брюс

2

Ви можете використовувати ExtendedXmlSerializer . Якщо у вас є клас:

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

і створити екземпляр цього класу:

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

Ви можете серіалізувати цей об’єкт за допомогою ExtendedXmlSerializer:

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

Вихід xml виглядатиме так:

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

Ви можете встановити ExtendedXmlSerializer з nuget або виконати таку команду:

Install-Package ExtendedXmlSerializer

Ось приклад в Інтернеті


0

Ця стаття пояснює, як саме впоратися з цим: Як мені ... Серіалізувати хеш-таблицю в C #, коли програма вимагає цього?

Я сподіваюся, що це корисно


Відповіді, що стосуються лише посилань, піддаються гниттю бітів, і їх слід узагальнити в самому органі відповіді із суттєвими деталями.
Крістофер Кайл Хортон
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.