Серийный класс, содержащий член словаря
Расширяя предыдущую проблему , я решил (де) сериализовать свой класс конфигурационных файлов, который отлично работал.
Теперь я хочу сохранить ассоциативный массив букв диска для сопоставления (клавиша - буква диска, значение - сетевой путь) и попытались использовать Dictionary
, HybridDictionary
и Hashtable
для этого, но я всегда получите следующую ошибку при вызове ConfigFile.Load()
или ConfigFile.Save()
:
Произошла ошибка, отражающая тип 'App.ConfigFile. [Надрез] System.NotSupportedException: невозможно член сериализации App.Configfile.mappedDrives [snip]
Из того, что я читал, словари и хэш-таблицы могут быть сериализованы, так что я делаю неправильно?
[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();
}
}
}
}
Ответы
Ответ 1
Вы не можете сериализовать класс, реализующий IDictionary. Проверьте ссылка.
В: Почему я не могу сериализовать хеш-таблицы?
A: XmlSerializer не может обрабатывать классы, реализующие IDictionary интерфейс. Отчасти это было связано с расписания и частично из-за тот факт, что хэш-таблица не имеют аналог типа XSD система. Единственным решением является реализовать пользовательскую хэш-таблицу, которая не реализовать IDictionary интерфейс.
Поэтому я думаю, что для этого вам нужно создать свою собственную версию словаря. Проверьте этот другой вопрос.
Ответ 2
Существует решение на Пол Уэлтер Веблог - 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
{
#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
}
Ответ 3
Вместо XmlSerializer
вы можете использовать System.Runtime.Serialization.DataContractSerializer
. Это может сериализовать словари и интерфейсы без пота.
Вот ссылка на полный пример, http://theburningmonk.com/2010/05/net-tips-xml-serialize-or-deserialize-dictionary-in-csharp/
Ответ 4
Создайте суррогат сериализации.
Например, у вас есть класс с общедоступным свойством типа Dictionary.
Чтобы поддерживать сериализацию 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 );
}
}
}
Ответ 5
Вам следует изучить 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
Словари и Hashtables не могут быть сериализованы с помощью XmlSerializer
. Поэтому вы не можете использовать их напрямую. Обходным путем было бы использовать атрибут XmlIgnore
, чтобы скрыть эти свойства из сериализатора и разоблачить их через список сериализуемых пар ключ-значение.
PS: построение XmlSerializer
очень дорогое, поэтому всегда кешируйте его, если есть вероятность, что сможете его повторно использовать.
Ответ 7
Мне нужен класс 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]);
}
}
}
}
Ответ 8
В этой статье объясняется, как справиться с этим:
Как мне... Сериализовать хеш-таблицу в С#, когда это требует приложение?
Я надеюсь, что это полезно
Ответ 9
класс словаря реализует 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+
Ответ 10
Вы можете использовать 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
Вот онлайн-пример