序列化包含字典成员的类

Posted

技术标签:

【中文标题】序列化包含字典成员的类【英文标题】:Serialize Class containing Dictionary member 【发布时间】:2010-10-04 11:36:48 【问题描述】:

扩展我的earlier problem,我决定(反)序列化我的配置文件类,效果很好。

我现在想存储要映射的驱动器号的关联数组(键是驱动器号,值是网络路径)并尝试使用 DictionaryHybridDictionaryHashtable,但我总是调用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】:

您可以使用 System.Runtime.Serialization 的 DataContractSerialize。这将能够序列化 IDictionary 和 Dictionary 成员。

https://docs.microsoft.com/en-us/dotnet/framework/wcf/samples/datacontractserializer-sample

在下面找到代码 sn-p。

public  ConfigFile ExtractConfigFileFromXml(string xmlPath)

    var serializer = new DataContractSerializer(typeof(ConfigFile));
    XmlReaderSettings settings = new XmlReaderSettings();
    settings.DtdProcessing = DtdProcessing.Parse;
    XmlReader reader = XmlReader.Create(xmlPath, settings);
    var confile = (ConfigFile)serializer.ReadObject(reader);
    return confile;
 

【讨论】:

【参考方案2】:

Paul Welter's Weblog - XML Serializable Generic Dictionary有解决方案

由于某种原因,.net 2.0 中的通用字典不是 XML 可序列化的。下面的代码 sn -p 是一个 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

【讨论】:

+1 很棒的答案。也适用于 SortedList,只需将“SerializableDictionary”更改为“SerializableSortedList”,将“Dictionary”更改为“SortedList”。 效果很好。上面接受的答案引用 MS 说“唯一的解决方案是实现一个不实现 IDictionary 接口的自定义哈希表”。但这不是真的吗?上述解决方案继承了 Dictionary(因此实现了 iDictionary),但也实现了 IXMLSerializable。还是我错过了什么? +1 和一个建议。当 SerializableDictionary 对象包含多个元素时,会引发异常... ReadXml() 和 WriteXml() 应修改为移动 ReadStartElement("item");和 WriteStartElement("item");及其关联的 ReadEndElement() 和 WriteEndElement() 退出 while 循环。 这是否意味着在以后的框架中 IDictionary 是可序列化的? 如果字典存储了 string 值,则此实现将起作用,但如果您尝试在其中存储自定义对象或字符串数​​组,则会在反序列化时抛出 InvalidOperationException 提及意外的包装器元素. (有关我遇到的问题的示例,请参阅my question。)【参考方案3】:

您可以使用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>

您可以从nuget 安装 ExtendedXmlSerializer 或运行以下命令:

Install-Package ExtendedXmlSerializer

这里是online example

【讨论】:

【参考方案4】:

Dictionary 类实现 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  

我认为这不是问题所在。请参阅下面的链接,该链接表示,如果您有任何其他不可序列化的数据类型,则 Dictionary 将不会被序列化。 http://forums.asp.net/t/1734187.aspx?Is+Dictionary+serializable+

【讨论】:

在最新版本中确实如此,但在 .NET 2 中,即使在今天,Dictionary 也无法序列化。我今天刚刚通过一个针对 .NET 3.5 的项目确认了这一点,这就是我找到这个线程的方式。【参考方案5】:

我想要一个使用 xml 属性作为键/值的 SerializableDictionary 类,所以我改编了 Paul Welter 的类。

这会产生像这样的 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]);
            
        
    

【讨论】:

看起来不错,但由于字典为空而失败。您需要 ReadXML 方法中的 reader.IsEmptyElement 测试。【参考方案6】:

您可以使用System.Runtime.Serialization.DataContractSerializer,而不是使用XmlSerializer。这可以毫不费力地序列化字典和接口。

这是一个完整示例的链接,http://theburningmonk.com/2010/05/net-tips-xml-serialize-or-deserialize-dictionary-in-csharp/

【讨论】:

DataContractSerializer 的问题在于它按字母顺序进行序列化和反序列化,因此如果您尝试以错误的顺序反序列化某些内容,它将在对象中出现空属性时静默失败【参考方案7】:

创建一个序列化代理。

例如,您有一个具有 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 );
            
        
    

【讨论】:

我喜欢这个,因为它将序列化与字典成员分离。如果我有一个频繁使用的类,我想向其中添加序列化功能,那么包装字典可能会导致继承类型中断。 对于任何实现此功能的人请注意:如果您尝试将代理属性设为 List(或任何其他 collection),则 XML 序列化程序 不会调用 setter (而是调用 getter 并尝试添加到返回的列表中,这显然不是您想要的)。这种模式坚持使用数组。【参考方案8】:

您应该探索 Json.Net,它非常易于使用,并且允许直接在 Dictionary 中反序列化 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

【讨论】:

【参考方案9】:

这篇文章详细解释了如何处理这个问题: How do I... Serialize a hash table in C# when the application requires it?

希望对你有帮助

【讨论】:

仅链接的答案可能会损坏,应在答案正文中总结基本细节。【参考方案10】:

字典和哈希表不能用XmlSerializer 序列化。因此,您不能直接使用它们。一种解决方法是使用 XmlIgnore 属性对序列化程序隐藏这些属性,并通过可序列化的键值对列表公开它们。

PS:构造一个XmlSerializer 非常昂贵,所以如果有机会再次使用它,请务必缓存它。

【讨论】:

【参考方案11】:

您不能序列化实现 IDictionary 的类。看看这个link。

问:为什么我不能序列化哈希表?

A:XmlSerializer 无法处理 实现 IDictionary 的类 界面。这部分是由于 进度限制,部分原因是 哈希表没有的事实 在 XSD 类型中有对应物 系统。唯一的解决办法是 实现一个自定义哈希表 不实现字典 界面。

所以我认为您需要为此创建自己的字典版本。检查这个other question。

【讨论】:

只是想知道DataContractSerializer 类可以做到这一点。只是输出有点难看。

以上是关于序列化包含字典成员的类的主要内容,如果未能解决你的问题,请参考以下文章

我可以使用 protobuf-net 在 Mono 中序列化一个对象(包含成员:​​字典、列表...等)并在 MS.NET 中反序列化它,反之亦然?

OpenCV 3 和 ArUco lib - 序列化字典

如何通过 getter 函数序列化包含指针的类?

如果无法使用 DataContractSerializer 反序列化密钥,则跳过字典条目

使用 Django ORM 序列化数据和相关成员

使用 protobuf-net 序列化具有接口类型成员的类