c# - XML 序列化和继承类型

标签 c# xml inheritance serialization xml-serialization

在我的 previous question 之后我一直致力于让我的对象模型序列化为 XML。但是我现在遇到了一个问题(quelle surprise!)。

我遇到的问题是我有一个集合,它是一个抽象基类类型,由具体的派生类型填充。

我认为只要将 XML 属性添加到所有涉及的类就可以了,一切都会很顺利。遗憾的是,事实并非如此!

所以我在 Google 上做了一些挖掘,现在我明白了为什么它不起作用。在那里,XmlSerializer 实际上在进行一些巧妙的反射,以便将对象序列化为 XML 或从 XML 序列化对象,并且由于它基于抽象类型,所以它无法弄清楚它到底在说什么。很好。

我确实遇到了this page在 CodeProject 上,它看起来可能会有很大帮助(尚未完全阅读/消费),但我想我也想把这个问题带到 StackOverflow 表中,看看你是否有任何巧妙的技巧/技巧,以便以最快/最轻的方式启动并运行它。

我还应该补充的一件事是,我不要想要走XmlInclude 路线。与它的耦合度太高了,而且系统的这个区域正在大量开发中,所以这将是一个真正令人头疼的维护问题!

最佳答案

问题解决了!

好的,所以我终于做到了(诚然,here 提供了很多的帮助!)。

总结一下:

目标:

  • 由于维护问题,我不想采用 XmlInclude 路线。
  • 一旦找到解决方案,我希望它能快速在其他应用程序中实现。
  • 可以使用抽象类型的集合,以及单个抽象属性。
  • 我真的不想为必须在具体类(class)中做“特殊”事情而烦恼。

确定的问题/注意事项:

  • XmlSerializer 做了一些很酷的反射,但是当涉及到抽象类型时它非常有限(即它只适用于抽象类型本身的实例,而不是子类)。
  • Xml 属性装饰器定义 XmlSerializer 如何处理它找到的属性。也可以指定物理类型,但这会在类和序列化程序之间产生紧密耦合(不好)。
  • 我们可以通过创建一个实现IXmlSerializable 的类来实现我们自己的 XmlSerializer。

解决方案

我创建了一个泛型类,您可以在其中将泛型类型指定为您将使用的抽象类型。这使类能够在抽象类型和具体类型之间“转换”,因为我们可以对转换进行硬编码(即我们可以获得比 XmlSerializer 可以获得的更多信息)。

然后我实现了 IXmlSerializable 接口(interface),这非常简单,但是在序列化时我们需要确保我们将具体类的类型写入 XML,以便我们可以在 de -序列化。同样重要的是要注意它必须是完全合格,因为这两个类所在的程序集可能不同。当然,这里需要进行一些类型检查和其他操作。

由于 XmlSerializer 无法转换,我们需要提供代码来执行此操作,因此隐式运算符会被重载(我什至不知道您可以这样做!)。

AbstractXmlSerializer 的代码是这样的:

using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Serialization;

namespace Utility.Xml
{
    public class AbstractXmlSerializer<AbstractType> : IXmlSerializable
    {
        // Override the Implicit Conversions Since the XmlSerializer
        // Casts to/from the required types implicitly.
        public static implicit operator AbstractType(AbstractXmlSerializer<AbstractType> o)
        {
            return o.Data;
        }

        public static implicit operator AbstractXmlSerializer<AbstractType>(AbstractType o)
        {
            return o == null ? null : new AbstractXmlSerializer<AbstractType>(o);
        }

        private AbstractType _data;
        /// <summary>
        /// [Concrete] Data to be stored/is stored as XML.
        /// </summary>
        public AbstractType Data
        {
            get { return _data; }
            set { _data = value; }
        }

        /// <summary>
        /// **DO NOT USE** This is only added to enable XML Serialization.
        /// </summary>
        /// <remarks>DO NOT USE THIS CONSTRUCTOR</remarks>
        public AbstractXmlSerializer()
        {
            // Default Ctor (Required for Xml Serialization - DO NOT USE)
        }

        /// <summary>
        /// Initialises the Serializer to work with the given data.
        /// </summary>
        /// <param name="data">Concrete Object of the AbstractType Specified.</param>
        public AbstractXmlSerializer(AbstractType data)
        {
            _data = data;
        }

        #region IXmlSerializable Members

        public System.Xml.Schema.XmlSchema GetSchema()
        {
            return null; // this is fine as schema is unknown.
        }

        public void ReadXml(System.Xml.XmlReader reader)
        {
            // Cast the Data back from the Abstract Type.
            string typeAttrib = reader.GetAttribute("type");

            // Ensure the Type was Specified
            if (typeAttrib == null)
                throw new ArgumentNullException("Unable to Read Xml Data for Abstract Type '" + typeof(AbstractType).Name +
                    "' because no 'type' attribute was specified in the XML.");

            Type type = Type.GetType(typeAttrib);

            // Check the Type is Found.
            if (type == null)
                throw new InvalidCastException("Unable to Read Xml Data for Abstract Type '" + typeof(AbstractType).Name +
                    "' because the type specified in the XML was not found.");

            // Check the Type is a Subclass of the AbstractType.
            if (!type.IsSubclassOf(typeof(AbstractType)))
                throw new InvalidCastException("Unable to Read Xml Data for Abstract Type '" + typeof(AbstractType).Name +
                    "' because the Type specified in the XML differs ('" + type.Name + "').");

            // Read the Data, Deserializing based on the (now known) concrete type.
            reader.ReadStartElement();
            this.Data = (AbstractType)new
                XmlSerializer(type).Deserialize(reader);
            reader.ReadEndElement();
        }

        public void WriteXml(System.Xml.XmlWriter writer)
        {
            // Write the Type Name to the XML Element as an Attrib and Serialize
            Type type = _data.GetType();

            // BugFix: Assembly must be FQN since Types can/are external to current.
            writer.WriteAttributeString("type", type.AssemblyQualifiedName);
            new XmlSerializer(type).Serialize(writer, _data);
        }

        #endregion
    }
}

那么,从那里开始,我们如何告诉 XmlSerializer 使用我们的序列化程序而不是默认序列化程序?我们必须在 Xml 属性类型属性中传递我们的类型,例如:

[XmlRoot("ClassWithAbstractCollection")]
public class ClassWithAbstractCollection
{
    private List<AbstractType> _list;
    [XmlArray("ListItems")]
    [XmlArrayItem("ListItem", Type = typeof(AbstractXmlSerializer<AbstractType>))]
    public List<AbstractType> List
    {
        get { return _list; }
        set { _list = value; }
    }

    private AbstractType _prop;
    [XmlElement("MyProperty", Type=typeof(AbstractXmlSerializer<AbstractType>))]
    public AbstractType MyProperty
    {
        get { return _prop; }
        set { _prop = value; }
    }

    public ClassWithAbstractCollection()
    {
        _list = new List<AbstractType>();
    }
}

在这里您可以看到,我们有一个集合和一个公开的属性,我们需要做的就是将 type 命名参数添加到 Xml 声明中,简单! :D

注意:如果您使用此代码,我将不胜感激。它还将有助于插入更多人加入社区:)

现在,但不确定如何处理此处的答案,因为它们各有利弊。我会更新那些我觉得有用的(没有冒犯那些没有用的)并在我有代表后关闭它 :)

有趣的问题,解决起来很有趣! :)

关于c# - XML 序列化和继承类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20084/

相关文章:

c# - AWS S3 如何将文件从一个存储桶移动到不同区域的另一个存储桶

java - Sax:XMLReader:将 CSV 流式传输并转换为 Xml,同时验证结果

java - 封装在Java模型类中

c++ - 如何重载继承类中的方法,以便基类看到继承的版本?

c# - 已安装 Outlook COM 加载项但未在 Outlook 中加载

c# - 如何获取 XML 或 XElement 变量中的特定元素计数

c# - 在 C# 中覆盖 Json 属性名称

c# - 使用带有 XPath 的 SelectSingleNode 返回 NULL

java - Android 在执行 HttpGet 请求时崩溃

java - 多级继承中的多个抽象类