XML序列化和继承类型

时间:2008-08-21 14:30:53

标签: c# xml inheritance serialization xml-serialization

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

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

我认为将XML属性添加到所涉及的所有类中都很好,一切都会很好。可悲的是,情况并非如此!

所以我已经在谷歌上做了一些挖掘,现在我明白为什么它不能正常工作。在那个中,XmlSerializer实际上正在做一些聪明的反思,以便将对象序列化到XML或从XML序列化,并且由于它基于抽象类型,它无法弄清楚它到底在说什么。细

我在CodeProject上遇到了this page,它看起来很可能有很多帮助(还没有完全阅读/消费),但我想我也想把这个问题带到StackOverflow表,到看看你是否有任何整洁的黑客/技巧,以便以最快/最轻的方式启动和运行。

我还应该补充的一点是,我不要想要沿着XmlInclude路线走下去。与它有太多的耦合,系统的这个区域正在大量开发,所以这将是一个真正的维护头痛!

7 个答案:

答案 0 :(得分:53)

解决了问题!

好的,所以我终于到了那里(诚然是here很多帮助!)。

总结一下:

目标:

  • 由于头痛,我不想走 XmlInclude 路线。
  • 一旦找到解决方案,我希望能够在其他应用程序中快速实施。
  • 可以使用抽象类型的集合,以及单独的抽象属性。
  • 我真的不想在具体课堂上做“特殊”的事情。

确定的问题/注意事项:

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

解决方案

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

然后我实现了 IXmlSerializable 接口,这很简单,但是在序列化时我们需要确保我们将具体类的类型写入XML,所以我们可以在de时将其转换回来-serializing。同样重要的是要注意它必须是完全限定,因为这两个类所在的程序集可能不同。当然有一些类型检查和需要在这里发生的东西。

由于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

注意:如果您使用此代码,我会非常感谢您的支持。它还有助于吸引更多人加入社区:)

现在,但不确定这里的答案是什么,因为他们都有他们的专业和骗子。我会upmod那些我认为有用的东西(对那些没有的东西没有冒犯)并且在我有代表后关闭它:)

有趣的问题和解决的好乐趣! :)

答案 1 :(得分:9)

要注意的一件事是,在XmlSerialiser构造函数中,您可以传递序列化程序可能难以解析的类型数组。我不得不多次使用那些需要序列化的集合或复杂数据结构集,而这些类型存在于不同的程序集等中。

XmlSerialiser Constructor with extraTypes param

编辑:我想补充一点,这种方法比XmlInclude属性等有好处,你可以找到一种在运行时发现和编译可能的具体类型列表的方法,并将它们填入。

答案 2 :(得分:3)

严重的是,可扩展的POCO框架永远不会可靠地序列化为XML。我这样说是因为我可以保证有人会来,扩展你的课程,然后把它搞砸了。

您应该考虑使用XAML来序列化对象图。它旨在实现此目的,而XML序列化则不是。

Xaml序列化程序和反序列化程序处理泛型没有问题,基类和接口的集合(只要集合本身实现IListIDictionary)。有一些注意事项,例如使用DesignerSerializationAttribute标记只读集合属性,但重新编写代码以处理这些极端情况并不难。

答案 3 :(得分:2)

快速更新一下,我没有忘记!

只是做一些更多的研究,看起来我是一个胜利者,只需要对代码进行排序。

到目前为止,我有以下内容:

  • XmlSeralizer 基本上是一个类,它对正在序列化的类进行一些漂亮的反思。它根据类型确定序列化的属性。
  • 问题发生的原因是因为正在发生类型不匹配,它期待 BaseType 但实际上接收 DerivedType ..虽然您可能认为它会对它进行多态处理,它不会因为它会涉及一整套额外的反射和类型检查,而它并不是设计用来做的。

通过创建代理类来充当序列化程序的中间人,似乎可以覆盖此行为(代码挂起)。这将基本确定派生类的类型,然后正常序列化。然后,此代理类将该行备份到主序列化器。

留意这个空间! ^ _ ^

答案 4 :(得分:2)

它肯定是您的问题的解决方案,但还有另一个问题,这有点破坏了您使用“可移植”XML格式的意图。当您决定在程序的下一个版本中更改类并且需要支持两种格式的序列化时都会发生不好的事情 - 新的和旧的格式(因为您的客户端仍然使用旧的文件/数据库,或者它们连接到您的服务器使用旧版本的产品)。但是你不能再使用这个序列号,因为你使用了

type.AssemblyQualifiedName

看起来像

TopNamespace.SubNameSpace.ContainingClass+NestedClass, MyAssembly, Version=1.3.0.0, Culture=neutral, PublicKeyToken=b17a5c561934e089

包含你的程序集属性和版本......

现在,如果您尝试更改程序集版本,或者您决定对其进行签名,则此反序列化将不起作用...

答案 5 :(得分:1)

我做过类似的事情。我通常做的是确保所有XML序列化属性都在具体类上,并且只需要在该类的属性上调用基类(如果需要)以检索在序列化程序调用时将被解除序列化的信息那些属性。这是一个更多的编码工作,但它确实比试图强制序列化器做正确的事情更好。

答案 6 :(得分:1)

更好的是,使用符号:

[XmlRoot]
public class MyClass {
    public abstract class MyAbstract {} 
    public class MyInherited : MyAbstract {} 
    [XmlArray(), XmlArrayItem(typeof(MyInherited))] 
    public MyAbstract[] Items {get; set; } 
}