当List引用多个使用自定义序列化的对象时,基本XML序列化失败

时间:2016-12-08 23:20:25

标签: c# xml serialization

我有一个类只能使用自定义XML序列化进行序列化,该序列化将用作使用基本序列化的更大系统中某些类的属性。我无法将整个系统转换为自定义序列化,因为它很大,并且将来可能包含使用基本序列化的第三方模块。

我的第一个问题是这是否允许。我在MSDN上看不到任何说明你不允许从基本序列化的对象引用自定义序列化对象的东西,如果你不能,它似乎是对代码可移植性的严重限制。然而,在这个网站上的早期答案似乎暗示你可能无法(海报说“你不能真的混合和匹配序列化不幸;一旦你实现IXmlSerializable,你拥有一切”)虽然我认为他是指的是课堂内的混合和匹配,你显然无法做到。 (看到: Mixing custom and basic serialization?

所以假设这实际上是允许的,我的问题是,只要主系统中的一个类实现了一个包含两个或多个具有自定义序列化的类的对象的List,它就会失败。

有趣的是,失败只发生在反序列化上,只有在对这样一个对象有多个引用时才会发生。更有趣的是,即使列表出现在依赖链的更上方(例如,列表包含普通对象,其中包含可能包含使用自定义序列化的对象的普通对象),它也会以相同的方式失败。

我写了一个小测试程序,演示了最简单的情况,如下所示。

所以我的问题是:

  1. 实际上是否允许引用自定义序列化对象 基本序列化的?
  2. 如果是的话,我做的是蠢事吗?
  3. 如果没有,这是一个已知的错误吗?
  4. 关于测试计划的说明: 数据类结构非常简单,由BasicXml类(使用基本序列化)和类CustomXml(使用自定义序列化)组成。 BasicXml包含CustomXml列表。 另一个类包含测试,它是独立的。只需实例化并运行RunTests()。

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.IO;
    using System.Xml;
    using System.Xml.Linq;
    using System.Xml.Schema;
    using System.Xml.Serialization;
    
    namespace AdHocTests
    {
        [Serializable]
        public class BasicXml
        {
            public List<CustomXml> TestList { get; set; }
        }
        [Serializable]
        public class CustomXml : IXmlSerializable
        {
            public CustomXml() { }
            public CustomXml(string name)
            {
                Name = name;
            }
            public string Name { get; set; }
            public void ReadXml(XmlReader reader)
            {
                Name = reader.ReadString();
            }
            public void WriteXml(XmlWriter writer)
            {
                writer.WriteString(Name);
            }
            public XmlSchema GetSchema()
            {
                return null; // I have removed this code for clarity
            }
        }
        public class MixedSerializationTest
        {
            public MixedSerializationTest()
            {
                _serializer.UnknownElement += UnknownElementHandler;
            }
            public void RunTests()
            {
                RunOneTest(makeItFail: false);
                RunOneTest(makeItFail: true);
            }
            private void RunOneTest(bool makeItFail)
            {
                Debug.Write("\n\nRUNNING TEST THAT WILL " + (makeItFail ? "FAIL" : "PASS") + ":\n\n");
                CustomXml c1 = new CustomXml("Hello");
                CustomXml c2 = new CustomXml("World");
                BasicXml b1 = new BasicXml
                {
                    TestList = makeItFail ? new List<CustomXml> { c1, c2 } : new List<CustomXml> { c1 }
                };
                XElement xml1 = GetXmlFromObject(b1);
                Debug.Write("Serialized XML:\n" + xml1.ToString() + "\n=====\n");
                BasicXml b2 = GetObjectFromXml(xml1);
                if (_cancelDeserialization) return;
                XElement xml2 = GetXmlFromObject(b2);
                Debug.Write("Reserialized XML:\n" + xml2.ToString() + "\n=====\n");
            }
            private XElement GetXmlFromObject(BasicXml obj)
            {
                using (StringWriter sw = new StringWriter())
                {
                    using (XmlWriter xw = XmlWriter.Create(sw))
                    {
                        _serializer.Serialize(xw, obj);
                        return XElement.Parse(sw.ToString());
                    }
                }
            }
            private BasicXml GetObjectFromXml(XElement xml)
            {
                using (StringReader sr = new StringReader(xml.ToString()))
                {
                    XmlWriterSettings settings = new XmlWriterSettings();
                    using (XmlReader xr = XmlReader.Create(sr))
                    {
                        return (BasicXml)_serializer.Deserialize(xr);
                    }
                }
            }
            private void UnknownElementHandler(object sender, XmlElementEventArgs e)
            {
                Debug.Write("\n*** Serializer threw an UnknownElement exception ***\n\n");
                _cancelDeserialization = true;
            }
            private XmlSerializer _serializer = new XmlSerializer(typeof(BasicXml));
            private bool _cancelDeserialization = false;
        }
    }
    

1 个答案:

答案 0 :(得分:1)

您当然可以包含传递给IXmlSerializable的较大对象图中包含的XmlSerializer类型的实例。

您的问题如下。在ReadXml()方法中,您需要拨打XmlReader.ReadElementContentAsString()而不是XmlReader.ReadString()

public void ReadXml(XmlReader reader)
{
    Name = reader.ReadElementContentAsString();
}

this answer中所述:

  

ReadXml方法必须使用WriteXml方法写入的信息重新构建对象。

     

调用此方法时,阅读器位于包含类型信息的元素的开头。也就是说,就在指示序列化对象开始的开始标记之前。当此方法返回时,它必须从头到尾读取整个元素,包括其所有内容。与WriteXml方法不同,框架不会自动处理包装元素。您的实施必须这样做。如果不遵守这些定位规则,可能会导致代码生成意外的运行时异常或损坏数据。

因此,您需要确保使用</CustomXml>结束元素标记。而且,根据ReadElementContentAsString()的{​​{3}},它可以满足您的需求:

  

此方法读取开始标记,元素的内容,并将阅读器移动到结束元素标记之外。

另一方面,ReadString()州的docs

  

我们建议您使用ReadElementContentAsString方法将元素或文本节点的内容作为字符串读取。

所以远离那个。