代码之家  ›  专栏  ›  技术社区  ›  Rob Cooper

XML序列化和继承的类型

  •  81
  • Rob Cooper  · 技术社区  · 17 年前

    从我的 previous question 我一直在努力使我的对象模型序列化为XML。但我现在遇到了一个问题(平息惊讶!).

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

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

    所以我在谷歌上做了一些挖掘,现在我明白了 为什么? 它不起作用。在那 这个 XmlSerializer 事实上,为了将对象序列化到XML或从XML序列化,它做了一些巧妙的反射,因为它基于抽象类型,所以它无法搞清楚它在说什么。 . 好的。

    我确实遇到过 this page 在codeproject上,这看起来可能会有很大帮助(还没有完全阅读/消费),但我想我也会把这个问题带到stackoverflow表上,看看您是否有任何巧妙的黑客/技巧,以便以最快/最轻的方式启动和运行。

    我还应该补充一点,我 想走下去 XmlInclude 路线。与它的耦合太多了,系统的这个领域正在进行大量的开发,所以这将是一个真正的维护难题!

    7 回复  |  直到 10 年前
        1
  •  53
  •   Rob Cooper    16 年前

    问题解决了!

    好吧,所以我终于到了那里 许多 帮助从 here !).

    所以总结一下:

    目标:

    • 我不想去 XMLI包含 因维修头痛而走的路线。
    • 一旦找到了一个解决方案,我希望它能够快速地在其他应用程序中实现。
    • 可以使用抽象类型集合以及单个抽象属性。
    • 我真的不想费心在具体的课堂上做“特殊”的事情。

    确定的问题/注意事项:

    • XML串行化器 做了一些很酷的思考,但是 非常 当涉及到抽象类型时受到限制(即,它只与抽象类型本身的实例一起工作,而不是子类)。
    • XML属性修饰符定义XML序列化程序如何处理其找到的属性。也可以指定物理类型,但这将创建 紧耦合 在类和序列化程序之间(不好)。
    • 我们可以通过创建一个实现 IXML可序列化 .

    解决方案

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

    然后我实现了 IXML可序列化 接口,这是非常直接的,但是在序列化时,我们需要确保将具体类的类型写入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
        }
    }
    

    那么,从那里,我们如何告诉XML序列化程序使用我们的序列化程序而不是默认的?我们必须在xml attributes 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>();
        }
    }
    

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

    注意:如果您使用此代码,我将非常感谢您的大声呼喊。它还将有助于推动更多人加入社区:)

    现在,但不确定如何处理这里的答案,因为他们都有自己的赞成和反对。我会更新那些我觉得有用的(对那些没有的人没有冒犯)并在我有代表后关闭它。

    有趣的问题和有趣的解决方法!:)

        2
  •  9
  •   Shaun Austin    17 年前

    需要注意的一点是,在xmlserialiser构造函数中,可以传递一个类型数组,而该类型数组可能难以解析。我已经使用过很多次了,其中一个集合或一组复杂的数据结构需要序列化,而这些类型存在于不同的程序集中等等。

    XmlSerialiser Constructor with extraTypes param

    编辑:我想补充一点,这种方法比XMLICLUDE属性等有好处,您可以找到一种方法,在运行时发现和编译可能的具体类型的列表,然后将它们塞进其中。

        3
  •  3
  •   Will    10 年前

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

    您应该研究使用XAML来序列化对象图。它的设计目的是这样做,而XML序列化则不是这样。

    XAML序列化程序和反序列化程序处理泛型时不会出现问题,也不会处理基类和接口的集合(只要集合本身实现 IList IDictionary )有一些警告,例如使用 DesignerSerializationAttribute 但是,重新编写代码来处理这些角落案例并不难。

        4
  •  2
  •   Rob Cooper    17 年前

    这只是一个快速的更新,我没有忘记!

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

    到目前为止,我有以下几点:

    • 这个 XMLSER 基本上是一个类,它对正在序列化的类进行一些漂亮的反射。它确定基于 类型 .
    • 出现问题的原因是发生了类型不匹配,它期望 基本类型 但事实上 派生类型 …虽然您可能认为它将以多态的方式处理它,但它不会涉及到整个额外的反射和类型检查负载,而这并不是它设计要做的。

    通过创建代理类作为序列化程序的中间层,似乎可以重写此行为(代码挂起)。这将基本上确定派生类的类型,然后按正常方式序列化。然后,这个代理类将把该XML返回到主序列化程序。

    注意这个地方!^ ^ ^

        5
  •  2
  •   Massimiliano    17 年前

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

    type.AssemblyQualifiedName
    

    看起来像是

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

    它包含程序集属性和版本…

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

        6
  •  1
  •   TheSmurf    17 年前

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

        7
  •  1
  •   user2009677    11 年前

    更好的是,使用符号:

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