XML 序列化和继承类型

Posted

技术标签:

【中文标题】XML 序列化和继承类型【英文标题】:XML Serialization and Inherited Types 【发布时间】:2010-09-06 09:53:56 【问题描述】:

从我的previous question 开始,我一直致力于让我的对象模型序列化为 XML。但我现在遇到了一个问题(平息惊喜!)。

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

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

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

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

我还应该补充一点,我想走XmlInclude 路线。与它的耦合实在是太多了,而且系统的这个领域正在大力开发,所以这将是一个真正的维护头痛!

【问题讨论】:

查看从您尝试序列化的类中提取的一些相关代码 sn-ps 会很有帮助。 伙伴:我重新打开是因为我觉得其他人会觉得这很有用,但如果您不同意,请随时关闭 有点困惑,因为这个帖子这么久没有任何东西了? 答案是:***.com/questions/6737666/… 【参考方案1】:

问题解决了!

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

总结一下:

目标:

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

发现的问题/注意点:

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

解决方案

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

然后我实现了 IXmlSerializable 接口,这非常简单,但是在序列化时我们需要确保将具体类的类型写入 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 attributes 类型属性中传递我们的类型,例如:

[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

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

现在,但不确定如何处理这里的答案,因为他们都有自己的优点和缺点。我会升级那些我觉得有用的东西(对那些没有用的人没有冒犯),一旦我有代表就关闭它:)

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

【讨论】:

前段时间我自己也遇到过这个问题。就个人而言,我最终放弃了 XmlSerializer 并直接使用 IXmlSerializable 接口,因为无论如何我的所有类都需要实现它。否则,解决方案非常相似。不过写得很好:) 我们使用 XML_ 属性将列表转换为数组 :) 因为动态实例化类需要一个无参数的构造函数。 您好!很长一段时间以来,我一直在寻找这样的解决方案。我认为它很棒!虽然我不知道如何使用它,你介意举个例子吗?你是在序列化你的类还是列表,包含你的对象? 不错的代码。请注意,可以将无参数构造函数声明为 privateprotected 以强制其他类无法使用它。【参考方案2】:

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

XmlSerialiser Constructor with extraTypes param

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

【讨论】:

这是我想要做的,但并不像我想的那样容易:***.com/questions/3897818/… 这是一篇非常古老的帖子,但对于任何想要像我们一样实现它的人,请注意 XmlSerializer 的构造函数带有 extraTypes 参数不缓存它动态生成的程序集。这花费了我们数周的时间来调试内存泄漏。因此,如果您要将额外类型与接受的答案代码一起使用,请缓存序列化程序。此处记录了此行为:support.microsoft.com/en-us/kb/886385【参考方案3】:

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

您应该考虑使用 XAML 来序列化您的对象图。它旨在做到这一点,而 XML 序列化不是。

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

【讨论】:

链接好像失效了 哦,好吧。我会用核弹那一点。关于该主题的大量其他资源。【参考方案4】:

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

只是做一些研究,看起来我是赢家,只需要对代码进行排序。

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

XmlSeralizer 基本上是一个对它正在序列化的类进行一些漂亮反映的类。它根据 Type 确定要序列化的属性。 出现问题的原因是因为发生了类型不匹配,它期待 BaseType 但实际上收到 DerivedType .. 虽然您可能认为它会多态地对待它,它不会,因为它会涉及到一个额外的反射和类型检查负载,而这不是它的设计目的。

这种行为似乎可以通过创建一个代理类作为序列化程序的中间人来覆盖(代码待定)。这将基本上确定派生类的类型,然后正常序列化。然后,该代理类将该 XML 备份到主序列化程序。

注意这个空间! ^_^

【讨论】:

【参考方案5】:

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

type.AssemblyQualifiedName

看起来像

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

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

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

【讨论】:

【参考方案6】:

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

【讨论】:

【参考方案7】:

更好的是,使用符号:

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

【讨论】:

如果您知道自己的课程,那就太好了,这是最优雅的解决方案。如果您从外部源加载新的继承类,那么您将无法使用它。

以上是关于XML 序列化和继承类型的主要内容,如果未能解决你的问题,请参考以下文章

在.net中序列化读写xml方法的总结

基类和继承类的 XML 序列化信息

继承和已知类型问题

day33 序列类型,绑定方法,类方法,静态方法,封装继承和多态

Xml 反序列化到对象,反射到类型

.net JSON 或 XML 对象序列化和包含字段类型的创建