XML序列化和继承的类型


85

在上一个问题之后,我一直在努力使我的对象模型序列化为XML。但是我现在遇到了一个问题(奇怪的是!)。

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

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

因此,我对Google进行了一些研究,现在我了解了为什么它不起作用。在XmlSerializer实际上是在做一些聪明的反射,以对象序列化到/从XML,并且因为它的基础上,抽象类型,它不能弄清楚到底是怎么回事了它的交谈。精细。

我确实在CodeProject上遇到过此页面,它看起来可能很有帮助(但仍可以完全阅读/使用),但是我想我也想将此问题带到StackOverflow表中,以查看是否有任何整洁的地方骇客/技巧,以便以最快/最轻便的方式启动并运行它。

有一件事我还要补充的是,我不要想往下走XmlInclude的路线。耦合太简单了,系统的这个区域正在大量开发中,因此这将是真正的维护难题!


1
查看从您要序列化的类中提取的一些相关代码段将很有帮助。
雷克斯M

伴侣:我重新开放是因为我认为其他人可以找到这个有用的东西,但是如果您不同意,可以随时关闭
JamesSugrue

有点困惑,因为这个线程上已经有这么长时间了吗?
罗伯·库珀

Answers:


54

问题解决了!

好的,所以我终于到达了那里(不可否认,这里很多帮助!)。

总结一下:

目标:

  • 我不想放弃XmlInclude由于维护麻烦,路线。
  • 找到解决方案后,我希望它能够在其他应用程序中快速实现。
  • 可以使用Abstract类型的集合以及各个抽象属性。
  • 我真的不想打扰在具体类中要做“特殊”的事情。

确定的问题/要点:

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

解决方案

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

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

由于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属性type属性中传递我们的类型,例如:

[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>();
    }
}

在这里,您可以看到,我们有一个集合和一个单独的属性,我们要做的就是将名为parameter的类型添加到Xml声明中,简单!:D

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

现在,但是不确定在这里如何处理答案,因为他们都有自己的优点和缺点。我会升级那些我认为有用的东西(对那些没有用的东西并没有冒犯),并在获得代表之后将其关闭:)

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


我前一段时间自己遇到了这个问题。就个人而言,我最终放弃了XmlSerializer并直接使用IXmlSerializable接口,因为无论如何我所有的类都需要实现它。否则,解决方案非常相似。虽然写得很好:)
Thorarin

我们使用XML_属性将列表转换为数组:)
Arcturus 2010年

2
因为需要一个无参数的构造函数才能动态实例化该类。
西拉斯·汉森

1
你好!我一直在寻找这样的解决方案已有一段时间了。我认为这太棒了!尽管我无法弄清楚如何使用它,但您介意举一个例子吗?您要序列化包含对象的类或列表吗?
丹尼尔(Daniel)

1
好的代码。请注意,可以声明无参数构造函数,privateprotected强制无参数构造函数不可用于其他类。
tcovo 2011年

9

要看的一件事是,在XmlSerialiser构造函数中,您可以传递序列化程序可能难以解析的类型数组。我不得不在需要序列化一个集合或一组复杂的数据结构的情况下使用了好几次,并且这些类型存在于不同的程序集中等等。

具有extraTypes参数的XmlSerialiser构造函数

编辑:我要补充一点,这种方法比XmlInclude属性具有优势,您可以找到一种在运行时发现和编译可能的具体类型列表并将其填充的方法。


这就是我想要做的,但它是不容易的,因为我在想:stackoverflow.com/questions/3897818/...
卢卡

这是一篇非常古老的文章,但是对于希望像我们一样实现此目标的任何人,请注意,具有extraTypes参数的XmlSerializer的构造函数不会缓存其动态生成的程序集。这花费了我们数周的调试内存泄漏的时间。因此,如果要在接受的答案的代码中使用额外的类型,请缓存序列化器。此行为记录在这里:support.microsoft.com/en-us/kb/886385
Julien Lebot

3

认真地说,POCO的可扩展框架永远不会可靠地序列化为XML。我之所以这样说,是因为我可以保证有人会参加,扩大您的课程并提高水平。

您应该研究使用XAML序列化对象图。它旨在实现此目的,而XML序列化并非如此。

Xaml序列化器和反序列化器可以毫无问题地处理泛型,以及基类和接口的集合(只要集合本身实现IListIDictionary)。有一些警告,例如用标记您的只读集合属性DesignerSerializationAttribute,但是重新编写代码以处理这些极端情况并不难。


链接似乎已死
bkribbs 2015年

那好吧。我会点点。有关该主题的大量其他资源。

2

只是对此的快速更新,我没有忘记!

只是做一些更多的研究,看起来我就像是赢家,只需要对代码进行排序即可。

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

  • XmlSeralizer基本上是一类在这日被序列化的类一些漂亮的反射。它确定基于Type序列化的属性。
  • 发生问题的原因是因为发生类型不匹配,它期望使用BaseType,但实际上接收到DerivedType ..虽然您可能认为它会进行多态处理,但它不会,因为它会涉及整个额外的负载反射和类型检查,这不是设计要做的。

通过创建代理类以充当序列化程序的中介,此行为似乎可以被覆盖(代码待处理)。基本上,这将确定派生类的类型,然后照常进行序列化。然后,该代理类将将该XML备份到主串行器中。

关注此空间!^ _ ^


2

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

type.AssemblyQualifiedName

看起来像

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

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

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


1

我已经做了类似的事情。我通常要做的是确保所有XML序列化属性都在具体的类上,并且仅使该类上的属性调用基类(在需要时)以检索在序列化程序调用时将反序列化的信息。这些属性。这需要更多的编码工作,但是比试图强制序列化器做正确的事情要好得多。


1

甚至更好,使用表示法:

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

2
如果您知道自己的课程,那就太好了,这是最优雅的解决方案。如果从外部源加载新的继承类,那么很遗憾不能使用它。
弗拉基米尔·
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.