XML序列化和inheritancetypes

从我以前的问题继续我一直在努力让我的对象模型序列化为XML。 但现在我遇到了一个问题(quelle惊喜!)。

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

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

所以我已经做了一些挖掘谷歌,我现在明白为什么它不工作。 因为XmlSerializer实际上是在进行一些聪明的reflection,以便将对象序列化到XML中,并且由于它基于抽象types,所以它无法弄清楚它到底在说什么 。 精细。

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

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

问题解决了!

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

所以总结一下:

目标:

  • 由于维护头痛,我不想沿着XmlInclude路线走。
  • 一旦find解决scheme,我希望在其他应用程序中快速实现。
  • 可以使用抽象types的集合,也可以使用单独的抽象属性。
  • 我真的不想在具体课上做“特殊”的事情。

确定的问题/注意事项:

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

解决scheme

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

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

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

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

在这里你可以看到,我们有一个集合和一个单独的属性被暴露,我们所要做的就是将名称参数types添加到Xml声明中,非常简单! :d

注意:如果你使用这个代码,我真的很感激大喊大叫。 它也将帮助更多的人到社区:)

现在,但不知道如何处理这里的答案,因为他们都有他们的亲和骗局。 我会修改那些我认为有用的东西(没有那些没有的攻击),closures它,一旦我有代表:)

有趣的问题和好玩的解决! 🙂

有一点需要注意的是,在XmlSerialiser构造函数中,您可以传递serialiser可能难以parsing的types数组。 我不得不多次使用这个集合或复杂的数据结构集合,这些数据结构需要被序列化,并且这些types存在于不同的程序集中。

具有extraTypes参数的XmlSerialiser构造函数

编辑:我会补充说,这种方法有利于超过XmlInclude属性等,你可以找出一种方法,在运行时发现和编译你可能的具体types的列表,并填入他们。

严重的是,一个可扩展的POCOs框架永远不会可靠地序列化到XML。 我这样说是因为我可以保证有人会来,扩大你的class级,并把它弄糟。

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

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

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

只是做更多的研究,看起来像我正在赢家,只需要得到的代码sorting。

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

  • XmlSeralizer基本上是一个对正在序列化的类进行一些漂亮reflection的类。 它确定基于types序列化的属性。
  • 问题发生的原因是因为types不匹配正在发生,它期待的BaseType,但实际上接收DerivedType ..虽然你可能认为它会对待它多态,它不会因为它会涉及一个额外的负载reflection和types检查,这是没有devise的。

通过创build代理类来充当序列化程序的中介,此行为似乎能够被覆盖(代码挂起)。 这将基本上确定派生类的types,然后正常序列化。 然后这个代理类将把这个XML提供给主序列化器。

看这个空间! ^ _ ^

这当然是您的问题的解决scheme,但还有一个问题,这有损于您使用“便携”的XML格式的意图。 当你决定在下一个版本的程序中改变类时,就会发生坏事,你需要同时支持两种格式的序列化 – 新旧格式(因为你的客户仍然使用旧的文件/数据库,或者连接到您的服务器使用旧版本的产品)。 但是你不能再使用这个序列化器了,因为你用过了

 type.AssemblyQualifiedName 

看起来像

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

这是包含您的程序集的属性和版本…

现在,如果您尝试更改您的程序集版本,或者您决定签名,则此反序列化不会奏效。

我做了类似的事情。 我通常所做的就是确保所有的XML序列化属性都在具体的类上,并且只要将该类的属性调用到基类(需要的时候)就可以获取将在序列化程序调用时被反序列化的信息那些属性。 这是一个更多的编码工作,但它比试图强制序列化器做正确的事情要好得多。

更好的是,使用符号:

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