C# Xml序列化Dictionary

一、背景

我在一个C#项目中需要用到将 Dictionary 序列化为 xml 的需求。一顿操作之后,代码报异常,说 Dictionary 不支持序列化,于是上网看看有没有前人做出了解决方法。

找了一下果然是有的,主要思路就是自己写一个支持序列化的 Dictionary 新类,继承原 DictionaryIXmlSerializable 接口,使新的 Dictionary 能够序列化。

二、主要代码

主要代码如下:

    [Serializable]
    public class DictionaryEx<TKey, TValue> : Dictionary<TKey, TValue>, IXmlSerializable {
        /// <summary>
        /// 默认构造函数
        /// </summary>
        public DictionaryEx() { }

        /// <summary>
        /// 序列化
        /// </summary>
        /// <param name="writer"></param>
        public void WriteXml(XmlWriter writer) {
            XmlSerializer KeySerializer = new XmlSerializer(typeof(TKey));
            XmlSerializer ValueSerializer = new XmlSerializer(typeof(TValue));

            foreach (KeyValuePair<TKey, TValue> kv in this) {
                writer.WriteStartElement("Item");
                writer.WriteStartElement("Key");
                KeySerializer.Serialize(writer, kv.Key);
                writer.WriteEndElement();
                writer.WriteStartElement("Value");
                ValueSerializer.Serialize(writer, kv.Value);
                writer.WriteEndElement();
                writer.WriteEndElement();
            }
        }

        /// <summary>
        /// 反序列化
        /// </summary>
        /// <param name="reader"></param>
        public void ReadXml(XmlReader reader) {
            reader.Read();
            XmlSerializer KeySerializer = new XmlSerializer(typeof(TKey));
            XmlSerializer ValueSerializer = new XmlSerializer(typeof(TValue));

            while (reader.NodeType != XmlNodeType.EndElement) {
                reader.ReadStartElement("Item");
                reader.ReadStartElement("Key");
                TKey tk = (TKey)KeySerializer.Deserialize(reader);
                reader.ReadEndElement();
                reader.ReadStartElement("Value");
                TValue vl = (TValue)ValueSerializer.Deserialize(reader);
                reader.ReadEndElement();
                reader.ReadEndElement();
                this.Add(tk, vl);
                reader.MoveToContent();
            }
            reader.ReadEndElement();
        }

        /// <summary>
        /// 取得概要
        /// 注:根据MSDN的文档,此方法为保留方法,一定返回 null。
        /// </summary>
        /// <returns>Xml概要</returns>
        public XmlSchema GetSchema() { return null; }
    }

代码并不复杂,我们自己写的 DictionaryEx 类继承了 Dictionary<TKey, TValue> 类和实现 IXmlSerializable 接口。

IXmlSerializable 接口要求实现下面3个方法:

  1. void WriteXml(XmlWriter writer) 序列化方法
  2. void ReadXml(XmlReader reader) 反序列化方法
  3. XmlSchema GetSchema() 保留方法,必须返回 null

首先是 void WriteXml(XmlWriter writer) 这个方法将在此类被 Xml 序列化时被调用,负责将当前对象写入 XmlWriter 中。所以在此方法里我们使用 foreach 遍历当前字典类中的所有键,将键和值序列化后写入 XmlWriter 中。

然后是 void ReadXml(XmlReader reader) 这个方法将在 Xml 文件被反序列化时被序列器调用,负责把 Xml 文件中的对应节点解析成指定的数据结构。

最后是 XmlSchema GetSchema() 这个方法是一个保留方法,根据MSDN的要求,直接返回 null

如果要做的更优雅一点话,可以把原 Dictionary 的构造函数都实现一遍,比如包含初始大小的构造函数、包含初始数据的构造函数等等。

例如,我们需要序列化的 List<ValueDisplay> 数据结构如下:

    List<ValueDisplay> _valDispalys = new List<ValueDisplay>();


    public class ValueDisplay {
        public string Name { get; set; }
        public DictionaryEx<uint, string> Values { get; set; }

        public ValueDisplay() { }

        public ValueDisplay(string name, DictionaryEx<uint, string> values) {
            Name = name;
            Values = values;
        }
    }

序列化后的结果:

<?xml version="1.0" encoding="utf-8"?>
<ArrayOfValueDisplay xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <ValueDisplay>
    <Name>STAT_ENCODE</Name>
    <Values>
      <Item>
        <Key>
          <unsignedInt>2</unsignedInt>
        </Key>
        <Value>
          <string>L3e-A3</string>
        </Value>
      </Item>
      <Item>
        <Key>
          <unsignedInt>1</unsignedInt>
        </Key>
        <Value>
          <string>L3e-A2</string>
        </Value>
      </Item>
    </Values>
  </ValueDisplay>
  <ValueDisplay>
    <Name>Protocol_ID</Name>
    <Values>
      <Item>
        <Key>
          <unsignedInt>1</unsignedInt>
        </Key>
        <Value>
          <string>ISO 27145-4</string>
        </Value>
      </Item>
    </Values>
  </ValueDisplay>
</ArrayOfValueDisplay>

功能是实现了,但是有点小问题,就是每一个 DictionaryEx<uint, string> 项的序列化结果十分繁琐,节点单独包含了数据类型信息(<unsignedInt></unsignedInt> <string></string>)。因为该序列化文件是用来控制软件界面信息的显示的,根据Name和key找到需要显示的字符串,根据不同的需求显示的内容也不同(例如支持不同语言的界面)。故该文件需要在后期由人工来维护,节点太繁琐的话就会增加人工维护的难度,也容易出错。

所以我想将上面的节点精简一下,改成形如:

    <Key>1</Key>
    <Value>ISO 27145-4</Value>

这种形式就简洁很多了。

三、代码改进

将上面 WriteXmlReadXml 两个函数稍微改动一下。

        /// <summary>  
        /// 序列化
        /// </summary>  
        /// <param name="writer"></param>  
        public void WriteXml(XmlWriter writer) {
            XmlSerializer kvSerializer = new XmlSerializer(typeof(Item));
            foreach (KeyValuePair<TKey, TValue> kv in this) {
                Item item = new Item {
                    Key = kv.Key,
                    Value = kv.Value
                };
                kvSerializer.Serialize(writer, item);
            }
        }

        /// <summary>  
        /// 反序列化
        /// </summary>  
        /// <param name="reader"></param>  
        public void ReadXml(XmlReader reader) {
            XmlSerializer kvSerializer = new XmlSerializer(typeof(Item));

            bool wasEmpty = reader.IsEmptyElement;
            reader.Read();
            if (wasEmpty) { return; }

            while (reader.NodeType != XmlNodeType.EndElement) {
                Item kv = (Item)kvSerializer.Deserialize(reader);
                this.Add(kv.Key, kv.Value);
                reader.MoveToContent();
            }

            reader.ReadEndElement();
        }

DictionaryEx 类中增加一个 Item 结构,用来表示 DictionaryEx 中的每一项

        public class Item {
            public TKey Key { get; set; }
            public TValue Value { get; set; }
        }

直接对这个 Item 结构进行序列化和反序列化。改进后的序列化结果为:

<?xml version="1.0" encoding="utf-8"?>
<ArrayOfValueDisplay xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <ValueDisplay>
    <Name>STAT_ENCODE</Name>
    <Values>
      <ItemOfUInt32String>
        <Key>2</Key>
        <Value>L3e-A3</Value>
      </ItemOfUInt32String>
      <ItemOfUInt32String>
        <Key>1</Key>
        <Value>L3e-A2</Value>
      </ItemOfUInt32String>
    </Values>
  </ValueDisplay>
  <ValueDisplay>
    <Name>Protocol_ID</Name>
    <Values>
      <ItemOfUInt32String>
        <Key>1</Key>
        <Value>ISO 27145-4</Value>
      </ItemOfUInt32String>
    </Values>
  </ValueDisplay>
</ArrayOfValueDisplay>

其结果为将key和value的数据类型信息移到item节点名称中来表示,这样看起来就简洁多了,人工编辑和查看也顺眼很多。

参考文章:

https://www.yanning.wang/archives/592.html

https://blog.csdn.net/woaixiaozhe/article/details/7873582


CashQian Blog

欢迎来到我的个人博客网站

友情链接

Powered by Python Blog. Copyright © 2016.

www.cashqian.net. All rights reserved.