如何(反)序列化具有字符串数组值的可序列化字典?

How do I (de)serialize a serializable dictionary with string array values?

我需要在 C# (.NET Framework 4.5.2) 中(反)序列化一个 class 到 XML ,它有一个字典 属性 和 string 键和 string[] 数组值。我在另一个问题上使用 this answer 中提到的 SerializableDictionary<TKey, TValue> 实现,所以这意味着我的 属性 是 SerializableDictionary<string, string[]>.

类型

将其序列化为 XML 文件看起来工作正常;但是,反序列化总是失败并显示 System.InvalidOperationException.

即使只是反序列化字典本身也会发生这种情况。请参阅下面重现问题的 MSTest 单元测试:

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using My.Namespace.Collections; // Where SerializableDictionary is defined.
using System.Xml.Serialization;
using System.IO;
using System.Linq;

namespace My.Namespace.Collections.Tests
{
    /// <summary>
    /// Provides unit test methods for the <see cref="SerializableDictionary{TKey, TValue}"/> class.
    /// </summary>
    [TestClass]
    public class SerializableDictionaryTests
    {
        /// <summary>
        /// A basic test that the <see cref="SerializableDictionary{TKey, TValue}"/> class has working
        /// (de)serialization.
        /// </summary>
        [TestMethod]
        [Timeout(100)]
        public void SerializableDictionary_BasicTest()
        {
            // Arrange.
            var sourceDictionary = new SerializableDictionary<string, string[]>();
            SerializableDictionary<string, string[]> destinationDictionary;
            var serializer = new XmlSerializer(typeof(SerializableDictionary<string, string[]>));

            var list1 = new string[] { "apple", "banana", "pear" };
            var list2 = new string[] { "carrot", "broccoli", "cauliflower", "onion" };
            var list3 = new string[] { "beef", "chicken" };

            sourceDictionary.Add("fruits", list1);
            sourceDictionary.Add("vegetables", list2);
            sourceDictionary.Add("meats", list3);

            // Act.
            using (var stream = new MemoryStream())
            {
                serializer.Serialize(stream, sourceDictionary);
                stream.Position = 0;
                destinationDictionary = (SerializableDictionary<string, string[]>)serializer.Deserialize(stream);
            }

            // Assert.
            // NOTE: We don't get this far because it crashes on the last statement above in the using block.
            Assert.AreEqual(3, destinationDictionary.Keys.Count);
            Assert.IsTrue(destinationDictionary.ContainsKey("fruits"));
            Assert.IsTrue(destinationDictionary.ContainsKey("vegetables"));
            Assert.IsTrue(destinationDictionary.ContainsKey("meats"));

            Assert.AreEqual(3, destinationDictionary["fruits"].Length);
            Assert.IsTrue(destinationDictionary["fruits"].Contains("apple"));
            Assert.IsTrue(destinationDictionary["fruits"].Contains("banana"));
            Assert.IsTrue(destinationDictionary["fruits"].Contains("pear"));

            Assert.AreEqual(4, destinationDictionary["vegetables"].Length);
            Assert.IsTrue(destinationDictionary["vegetables"].Contains("carrot"));
            Assert.IsTrue(destinationDictionary["vegetables"].Contains("broccoli"));
            Assert.IsTrue(destinationDictionary["vegetables"].Contains("cauliflower"));
            Assert.IsTrue(destinationDictionary["vegetables"].Contains("onion"));

            Assert.AreEqual(2, destinationDictionary["meats"].Length);
            Assert.IsTrue(destinationDictionary["meats"].Contains("beef"));
            Assert.IsTrue(destinationDictionary["meats"].Contains("chicken"));
        }
    }
}

异常发生在 serializer.Deserialize 调用处,内容为:

Test method My.Namespace.Collections.Tests.SerializableDictionaryTests.SerializableDictionary_BasicTest threw exception: 
System.InvalidOperationException: There is an error in XML document (8, 8). ---> System.InvalidOperationException: There is an error in XML document (8, 8). ---> System.InvalidOperationException: <ArrayOfString xmlns=''> was not expected.

为什么我会收到此异常,我该如何避免?如果可能的话,我想避免只为这个 属性 求助于自定义 XML 序列化。

编辑#1:

我制作了一个运行上述构建和序列化代码的小型控制台程序,然后使用 FileStream class 将生成的 XML 写入文件。这是它的内容:

<?xml version="1.0"?>
<dictionary>
  <item>
    <key>
      <string>fruits</string>
    </key>
    <value>
      <ArrayOfString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
        <string>apple</string>
        <string>banana</string>
        <string>pear</string>
      </ArrayOfString>
    </value>
  </item>
  <item>
    <key>
      <string>vegetables</string>
    </key>
    <value>
      <ArrayOfString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
        <string>carrot</string>
        <string>broccoli</string>
        <string>cauliflower</string>
        <string>onion</string>
      </ArrayOfString>
    </value>
  </item>
  <item>
    <key>
      <string>meats</string>
    </key>
    <value>
      <ArrayOfString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
        <string>beef</string>
        <string>chicken</string>
      </ArrayOfString>
    </value>
  </item>
</dictionary>

编辑#2:

在我的测试中将 SerializableDictionary<TKey, TValue> 声明的 TValue 类型更改为不同的东西并检查它是否有效后,我可以确认如果 TValuestring,但是如果将它设置为我的自定义可序列化 class,即使 class 装饰有 SerializableAttribute 或实现 IXmlSerializable 接口。所以,问题实际上比字符串数组更大。

在为此苦苦挣扎了几天之后,我得出的结论是,我使用的 Serializable<TKey, TValue> 实现无法处理数组和其他对象。

我求助于在 class 上实现 IXmlSerializable 接口,在 WriteXml 方法中将字典值序列化为列表。在 ReadXml 中,我简单地反序列化存储的列表,然后将其值放回字典中,同时检查列表项上是否没有作为附加 属性 存储的重复键。它很笨重,但它确实有效。

很高兴您找到了解决方案并成功运行。不确定您是否仍然对解决方案持开放态度。尽管如此,您可以按照原样使用 Dictionary 进行以下操作。

尽管 .NET 的 XmlSerializer 不支持字典,但有一些库可以为您完成。一个这样的库是 SharpSerializer (Source, NuGet),它将任何类型序列化为二进制或 XML。

用法 就像:

var serializer = new SharpSerializer(); 
serializer.Serialize(dict, "test.xml");

输入

var dict = new Dictionary<string, string[]> 
{
    { "nikhil", new string[] { "nikhil.0@so.com", "nikhil.1@so.com" } } 
};

输出

<Dictionary name="Root" type="System.Collections.Generic.Dictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
  <Items>
    <Item>
      <Simple value="nikhil" />
      <SingleArray>
        <Items>
          <Simple value="nikhil.0@so.com" />
          <Simple value="nikhil.1@so.com" />
        </Items>
      </SingleArray>
    </Item>
  </Items>
</Dictionary>

Fiddle