Classe de serialização contendo membro do dicionário


144

Expandindo meu problema anterior , decidi (de) serializar minha classe de arquivo de configuração, que funcionou muito bem.

Agora eu quero armazenar uma matriz associativa de letras de unidade para mapear (chave é a letra da unidade, o valor é o caminho de rede) e tentei usar Dictionary, HybridDictionarye Hashtablepara isso, mas eu sempre obter o seguinte erro ao chamar ConfigFile.Load()ou ConfigFile.Save():

Ocorreu um erro ao refletir o tipo 'App.ConfigFile'. [snip] System.NotSupportedException: não é possível serializar o membro App.Configfile.mappedDrives [snip]

Pelo que li, os Dicionários e o HashTables podem ser serializados, então o que estou fazendo de errado?

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

Respostas:


77

Você não pode serializar uma classe que implementa o IDictionary. Confira este link .

P: Por que não posso serializar hashtables?

R: O XmlSerializer não pode processar classes implementando a interface IDictionary. Isso ocorreu em parte devido a restrições de cronograma e em parte devido ao fato de uma tabela de hash não ter uma contrapartida no sistema do tipo XSD. A única solução é implementar uma hashtable personalizada que não implementa a interface IDictionary.

Então, acho que você precisa criar sua própria versão do dicionário para isso. Verifique esta outra questão .


4
Apenas imaginando que a DataContractSerializerclasse pode fazer isso. Apenas a saída é um pouco feia.
Rekire

186

Há uma solução no Weblog - Dicionário Genérico Serializável XML de Paul Welter

Por alguma razão, o dicionário genérico no .net 2.0 não é XML serializável. O seguinte snippet de código é um dicionário genérico serializável em xml. O dicionário pode ser serializado implementando a interface 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. Ei, por que o Stack Overflow não possui um botão de código de cópia? Humm? porque esse código vale a pena copiar!
toddmo

1
+1 resposta fantástica. Também funciona para SortedList, apenas alterou o "SerializableDictionary" para "SerializableSortedList" e o "Dictionary <TKey, TValue>" para "SortedList <TKey, TValue>".
Kdmurray

1
+1 e uma sugestão. Quando um objeto SerializableDictionary contém mais de um elemento, é lançada uma exceção ... ReadXml () e WriteXml () devem ser modificados para mover ReadStartElement ("item"); e WriteStartElement ("item"); e seus ReadEndElement () e WriteEndElement () associados fora do loop while.
MNS

1
Isso significa que, em estruturas posteriores, o IDictionary é serializável?
Thomas

1
Essa implementação funcionará se o dicionário estiver armazenando, digamos, stringvalores, mas acionará uma InvalidOperationExceptiondesserialização mencionando um elemento inesperado do wrapper se você tentar armazenar objetos personalizados ou matrizes de strings nele. (Veja a minha pergunta para um exemplo dos problemas que eu enfrentei com isso.)
Christopher Kyle Horton

57

Em vez de usar, XmlSerializervocê pode usar a System.Runtime.Serialization.DataContractSerializer. Isso pode serializar dicionários e interfaces sem suar.

Aqui está um link para um exemplo completo, http://theburningmonk.com/2010/05/net-tips-xml-serialize-or-deserialize-dictionary-in-csharp/


2
Melhor resposta, mãos para baixo.
DWRoelands

Concordado, esta é a melhor resposta. Limpo, simples e SECO (não se repita).
Nolonar

Problema com DataContractSerializer é que ele serializa e desserializa em ordem alfabética por isso, se você tentar deserialize algo na ordem errada ele irá falhar silenciosamente com propriedades nulos em seu objeto
superjugy

14

Crie um substituto de serialização.

Exemplo, você tem uma classe com propriedade pública do tipo Dictionary.

Para oferecer suporte à serialização Xml desse tipo, crie uma classe de valor-chave genérica:

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

Adicione um atributo XmlIgnore à sua propriedade original:

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

Exponha uma propriedade pública do tipo matriz, que contém uma matriz de instâncias SerializableKeyValue, que são usadas para serializar e desserializar na propriedade 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 );
            }
        }
    }

Eu gosto disso porque desacopla a serialização do membro do dicionário. Se eu tivesse uma classe muito usada na qual desejasse adicionar recursos de serialização, a quebra do dicionário poderia causar uma interrupção nos tipos herdados.
VoteCoffee

Uma palavra de cautela para quem implementa isso: se você tentar tornar sua propriedade substituta uma lista (ou qualquer outra coleção ), o serializador XML não chamará o setter (em vez disso, chamará o getter e tentará adicionar à lista retornada, o que obviamente não é o que você queria). Atenha-se às matrizes para esse padrão.
Fraxtil

9

Você deve explorar o Json.Net, bastante fácil de usar e permitir que os objetos Json sejam desserializados diretamente no Dictionary.

james_newtonking

exemplo:

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

Dicionários e Hashtables não podem ser serializados com XmlSerializer. Portanto, você não pode usá-los diretamente. Uma solução alternativa seria usar o XmlIgnoreatributo para ocultar essas propriedades do serializador e expô-las por meio de uma lista de pares de valores-chave serializáveis.

PS: construir um XmlSerializeré muito caro, portanto, sempre o armazene em cache se houver uma chance de poder reutilizá-lo.


4

Eu queria uma classe SerializableDictionary que usasse atributos xml para key / value, então adaptei a classe de Paul Welter.

Isso produz xml como:

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

Código:

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

Testes unitários:

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
Parece bom, mas falha com um dicionário vazio. Você precisa do teste reader.IsEmptyElement no método ReadXML.
AnthonyVO 17/09/15

2

a classe Dictionary implementa ISerializable. A definição de dicionário de classes é dada abaixo.

[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  

Eu não acho que esse seja o problema. consulte o link abaixo, que diz que se você estiver tendo outro tipo de dados que não seja serializável, o Dicionário não será serializado. http://forums.asp.net/t/1734187.aspx?Is+Dictionary+serializable+


Isso é verdade nas versões mais recentes, mas no .NET 2, o Dictionary não é serializável até hoje. Confirmei hoje mesmo com um projeto direcionado ao .NET 3.5, que foi como eu encontrei esse segmento.
28718 Bruce Bruce

2

Você pode usar o ExtendedXmlSerializer . Se você tem uma aula:

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 crie uma instância desta 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");

Você pode serializar esse objeto usando o ExtendedXmlSerializer:

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

O xml de saída será semelhante 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>

Você pode instalar o ExtendedXmlSerializer a partir do nuget ou executar o seguinte comando:

Install-Package ExtendedXmlSerializer

Aqui está um exemplo online


Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.