一、扯淡背景
本人一直有个习惯就是开发的时候把一些程序需要使用的后期可能会变动的配置做成Xml写到本地文件里,当每次程序启动的时候再读取出来,以免因为配置变动导致重新修改代码。
最近的一次更新调整中,需要在配置类中加入字典类Dictionary<TKey, TValue>。
结果不加不要紧,一加这个字典类,在序列化为Xml的时候直接抛出了异常:
上网搜了一下,发现这个问题非常普遍。解决办法有一堆,但具体原因并不清楚。
本人去简单的翻了一下.net的源码实现,发现.net在初始化XmlSerializer的时候会进行类型检查,如果发现传入的类型实现了IDictionary接口,就会抛出NotSupportedException异常:
二、解决方案
虽然不知道微软为什么明令禁止实现了IDictionary的类进行Xml序列化和反序列化,但这个问题也有解决的方法,那就是创建一个新的类,继承Dictionary<TKey, TValue>类的同时实现IXmlSerializable接口。在这里我将新建的类名命名为:DictionaryEx,具体的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 |
using System.Xml; using System.Xml.Schema; using System.Xml.Serialization; namespace System.Collections.Generic { #region 可序列化字典类 + public class DictionaryEx<TKey, TValue> /// <summary> /// 可序列化字典类 /// </summary> /// <typeparam name="TKey">键泛型</typeparam> /// <typeparam name="TValue">值泛型</typeparam> [Serializable] public class DictionaryEx<TKey, TValue> : Dictionary<TKey, TValue>, IXmlSerializable { #region 构造函数 #region 默认构造函数 + public DictionaryEx() /// <summary> /// 默认构造函数 /// </summary> public DictionaryEx() : base() { } #endregion #region 构造函数 + public DictionaryEx(int capacity) /// <summary> /// 构造函数 /// </summary> /// <param name="capacity">可包含的初始元素数</param> public DictionaryEx(int capacity) : base(capacity) { } #endregion #region 构造函数 + public DictionaryEx(IEqualityComparer<TKey> comparer) /// <summary> /// 构造函数 /// </summary> /// <param name="comparer">比较键时要使用的 比较器 实现,或者为 null,以便为键类型使用默认的 比较器</param> public DictionaryEx(IEqualityComparer<TKey> comparer) : base(comparer) { } #endregion #region 构造函数 + public DictionaryEx(IDictionary<TKey, TValue> dictionary) /// <summary> /// 构造函数 /// </summary> /// <param name="dictionary">初始数据</param> public DictionaryEx(IDictionary<TKey, TValue> dictionary) : base(dictionary) { } #endregion #region 构造函数 + public DictionaryEx(int capacity, IEqualityComparer<TKey> comparer) /// <summary> /// 构造函数 /// </summary> /// <param name="capacity">可包含的初始元素数</param> /// <param name="comparer">比较键时要使用的 比较器 实现,或者为 null,以便为键类型使用默认的 比较器</param> public DictionaryEx(int capacity, IEqualityComparer<TKey> comparer) : base(capacity, comparer) { } #endregion #region 构造函数 + public DictionaryEx(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer) /// <summary> /// 构造函数 /// </summary> /// <param name="dictionary">初始数据</param> /// <param name="comparer">比较键时要使用的 比较器 实现,或者为 null,以便为键类型使用默认的 比较器</param> public DictionaryEx(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer) : base(dictionary, comparer) { } #endregion #endregion #region 取得概要 + public XmlSchema GetSchema() /// <summary> /// 取得概要 /// 注:根据MSDN的文档,此方法为保留方法,一定返回 null。 /// </summary> /// <returns>Xml概要</returns> public XmlSchema GetSchema() { return null; } #endregion #region 从 XML 对象中反序列化生成本对象 + public void ReadXml(XmlReader reader) /// <summary> /// 从 XML 对象中反序列化生成本对象 /// </summary> /// <param name="reader">包含反序列化对象的 XmlReader 流</param> public void ReadXml(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 != 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(); } #endregion #region 将本对象序列化为 XML 对象 + public void WriteXml(XmlWriter writer) /// <summary> /// 将本对象序列化为 XML 对象 /// </summary> /// <param name="writer">待写入的 XmlWriter 对象</param> public void WriteXml(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 } #endregion } |
三、代码解释
上面代码其实并不复杂,首先我们继承了Dictionary<TKey, TValue>类,所以我们需要根据Dictionary<TKey, TValue>类的构造函数,写出对应的构造函数。
然后就是实现IXmlSerializable接口。IXmlSerializable接口要求实现这三个方法:
1 2 3 |
XmlSchema GetSchema(); void ReadXml(XmlReader reader); void WriteXml(XmlWriter writer); |
首先XmlSchema GetSchema();这个方法是一个保留方法,根据MSDN的要求,直接返回null。
然后是void WriteXml(XmlWriter writer);方法,这个方法将在此类被Xml序列化时被调用,负责将当前对象写入XmlWriter中。所以在此方法里我们使用foreach遍历当前字典类中的所有键,将键和值序列化后写入XmlWriter中。
最后就是void ReadXml(XmlReader reader); 了。这个方法将在Xml文件被反序列化时被序列器调用,负责将Xml文件中的对应节点一一返回。
四、实际运行展示
测试用的可Xml序列化字典类及数据:
序列化结果:
从文件加载并反序列化Xml,输出字典内容:
运行结果:
附
1.DictionaryEx:链接:http://pan.baidu.com/s/1nvtA5Xf 密码:irqx
2.XmlHelper(Xml序列化及反序列化辅助类):链接:http://pan.baidu.com/s/1sl5DCHR 密码:5kif
小柊
2017年9月16日 17:49:33
1 条评论发布于 “C# Xml序列化Dictionary类”