【问题标题】:Basic XML serialization fails when a List references more than one object that uses custom serialization当 List 引用多个使用自定义序列化的对象时,基本 XML 序列化失败
【发布时间】:2016-12-09 08:54:47
【问题描述】:

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

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

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

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

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

所以我的问题是:

  1. 实际上是否允许引用自定义序列化对象 基本序列化的?
  2. 如果是,我是不是在做傻事?
  3. 如果不是,这是一个已知的错误吗?

测试程序注意事项: 数据类结构非常简单,由 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;
    }
}

【问题讨论】:

  • 请注意,如果 CustomXml 实现 IXmlSerializable,它可以工作,这表明您遗漏了一些东西。
  • minimal reproducible example!我能够立即重现该问题。
  • 您好 RamblinRose,移除 IXmlSerializable 接口是将其转回基本序列化而不是自定义的开关,所以是的,它可以工作,因为它已转回正常的基本 + 基本情况。当接口到位时(表示基本+自定义情况)它几乎一直工作,并且总是完美地序列化,这表明代码可能是正确的。只是它在我布置的非常特殊的情况下做出了这种奇怪的事情。
  • 感谢您的反馈,dbc

标签: c# xml serialization


【解决方案1】:

您当然可以包含IXmlSerializable 类型的实例,这些实例包含在传递给XmlSerializer 的一些更大的对象图中。

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

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

this answer中所述:

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

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

因此,您需要确保使用 &lt;/CustomXml&gt; 结束元素标记。而且,根据docs for ReadElementContentAsString(),它可以满足您的需求:

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

另一方面,docsReadString() 状态:

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

所以远离那个。

【讨论】:

  • 太棒了,感谢 dbc,一个小改动就完全修复了它,它也解释了为什么我看到的是“意外的运行时异常或损坏的数据”——总是很难确定!还有一个非常清楚的解释,谢谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多