C# XML 序列化和继承类型

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/20084/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-01 09:04:32  来源:igfitidea点击:

XML Serialization and Inherited Types

提问by Rob Cooper

Following on from my previous questionI have been working on getting my object model to serialize to XML. But I have now run into a problem (quelle surprise!).

继我之前的问题之后,我一直致力于让我的对象模型序列化为 XML。但是我现在遇到了一个问题(令人惊讶!)。

The problem I have is that I have a collection, which is of a abstract base class type, which is populated by the concrete derived types.

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

I thought it would be fine to just add the XML attributes to all of the classes involved and everything would be peachy. Sadly, thats not the case!

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

So I have done some digging on Google and I now understand whyit's not working. In that the XmlSerializeris in fact doing some clever reflection in order to serialize objects to/from XML, and since its based on the abstract type, it cannot figure out what the hell it's talking to. Fine.

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

I did come across this pageon CodeProject, which looks like it may well help a lot (yet to read/consume fully), but I thought I would like to bring this problem to the StackOverflow table too, to see if you have any neat hacks/tricks in order to get this up and running in the quickest/lightest way possible.

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

One thing I should also add is that I DO NOTwant to go down the XmlIncluderoute. There is simply too much coupling with it, and this area of the system is under heavy development, so the it would be a real maintenance headache!

有一件事我还要补充的是,我不要想往下走XmlInclude的路线。与它的耦合实在是太多了,而且系统的这个区域正在大量开发中,所以这将是一个真正令人头疼的维护问题!

采纳答案by Rob Cooper

Problem Solved!

问题解决了!

OK, so I finally got there (admittedly with a lotof help from here!).

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

So summarise:

所以总结一下:

Goals:

目标:

  • I didn't want to go down the XmlIncluderoute due to the maintenence headache.
  • Once a solution was found, I wanted it to be quick to implement in other applications.
  • Collections of Abstract types may be used, as well as individual abstract properties.
  • I didn't really want to bother with having to do "special" things in the concrete classes.
  • 由于维护问题,我不想走XmlInclude路线。
  • 找到解决方案后,我希望它能够在其他应用程序中快速实现。
  • 可以使用抽象类型的集合,以及单独的抽象属性。
  • 我真的不想费心在具体的类中做“特殊”的事情。

Identified Issues/Points to Note:

已确定的问题/注意事项:

  • XmlSerializerdoes some pretty cool reflection, but it is verylimited when it comes to abstract types (i.e. it will only work with instances of the abstract type itself, not subclasses).
  • The Xml attribute decorators define how the XmlSerializer treats the properties its finds. The physical type can also be specified, but this creates a tight couplingbetween the class and the serializer (not good).
  • We can implement our own XmlSerializer by creating a class that implements IXmlSerializable.
  • XmlSerializer做了一些非常酷的反射,但是当涉及到抽象类型时它是非常有限的(即它只适用于抽象类型本身的实例,而不是子类)。
  • Xml 属性装饰器定义 XmlSerializer 如何处理它找到的属性。也可以指定物理类型,但这会在类和序列化程序之间造成紧密耦合(不好)。
  • 我们可以通过创建一个实现IXmlSerializable的类来实现我们自己的 XmlSerializer 。

The Solution

解决方案

I created a generic class, in which you specify the generic type as the abstract type you will be working with. This gives the class the ability to "translate" between the abstract type and the concrete type since we can hard-code the casting (i.e. we can get more info than the XmlSerializer can).

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

I then implemented the IXmlSerializableinterface, this is pretty straight forward, but when serializing we need to ensure we write the type of the concrete class to the XML, so we can cast it back when de-serializing. It is also important to note it must be fully qualifiedas the assemblies that the two classes are in are likely to differ. There is of course a little type checking and stuff that needs to happen here.

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

Since the XmlSerializer cannot cast, we need to provide the code to do that, so the implicit operator is then overloaded (I never even knew you could do this!).

由于 XmlSerializer 无法强制转换,我们需要提供代码来执行此操作,因此隐式运算符随后会被重载(我什至不知道您可以这样做!)。

The code for the AbstractXmlSerializer is this:

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
    }
}

So, from there, how do we tell the XmlSerializer to work with our serializer rather than the default? We must pass our type within the Xml attributes type property, for example:

那么,从那里开始,我们如何告诉 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>();
    }
}

Here you can see, we have a collection and a single property being exposed, and all we need to do is add the typenamed parameter to the Xml declaration, easy! :D

在这里你可以看到,我们有一个集合和一个被暴露的属性,我们需要做的就是将类型命名参数添加到 Xml 声明中,很简单!:D

NOTE: If you use this code, I would really appreciate a shout-out. It will also help drive more people to the community :)

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

Now, but unsure as to what to do with answers here since they all had their pro's and con's. I'll upmod those that I feel were useful (no offence to those that weren't) and close this off once I have the rep :)

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

Interesting problem and good fun to solve! :)

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

回答by TheSmurf

I've done things similar to this. What I normally do is make sure all the XML serialization attributes are on the concrete class, and just have the properties on that class call through to the base classes (where required) to retrieve information that will be de/serialized when the serializer calls on those properties. It's a bit more coding work, but it does work much better than attempting to force the serializer to just do the right thing.

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

回答by Shaun Austin

One thing to look at is the fact that in the XmlSerialiser constructor you can pass an array of types that the serialiser might be having difficulty resolving. I've had to use that quite a few times where a collection or complex set of datastructures needed to be serialised and those types lived in different assemblies etc.

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

XmlSerialiser Constructor with extraTypes param

带有 extraTypes 参数的 XmlSerialiser 构造函数

EDIT: I would add that this approach has the benefit over XmlInclude attributes etc that you can work out a way of discovering and compiling a list of your possible concrete types at runtime and stuff them in.

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

回答by Shaun Austin

Seriously, an extensible framework of POCOs will never serialize to XML reliably. I say this because I can guarantee someone will come along, extend your class, and botch it up.

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

You should look into using XAML for serializing your object graphs. It is designed to do this, whereas XML serialization isn't.

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

The Xaml serializer and deserializer handles generics without a problem, collections of base classes and interfaces as well (as long as the collections themselves implement IListor IDictionary). There are some caveats, such as marking your read only collection properties with the DesignerSerializationAttribute, but reworking your code to handle these corner cases isn't that hard.

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

回答by Rob Cooper

Just a quick update on this, I have not forgotten!

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

Just doing some more research, looks like I am on to a winner, just need to get the code sorted.

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

So far, I have the following:

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

  • The XmlSeralizeris basically a class that does some nifty reflection on the classes it is serializing. It determines the properties that are serialized based on the Type.
  • The reason the problem occurs is because a type mismatch is occurring, it is expecting the BaseTypebut in fact receives the DerivedType.. While you may think that it would treat it polymorphically, it doesn't since it would involve a whole extra load of reflection and type-checking, which it is not designed to do.
  • XmlSeralizer基本上是一类在这日被序列化的类一些漂亮的反射。它确定基于Type序列化的属性。
  • 出现问题的原因是因为发生了类型不匹配,它期待BaseType但实际上接收的是DerivedType.. 虽然您可能认为它会以多态方式处理它,但它不会,因为它会涉及整个额外的负载反射和类型检查,它不是设计用来做的。

This behaviour appears to be able to be overridden (code pending) by creating a proxy class to act as the go-between for the serializer. This will basically determine the type of the derived class and then serialize that as normal. This proxy class then will feed that XML back up the line to the main serializer..

通过创建一个代理类作为序列化程序的中间人,这种行为似乎能够被覆盖(代码挂起)。这将基本上确定派生类的类型,然后照常对其进行序列化。然后,此代理类会将 XML 反馈到主序列化器的行中。

Watch this space! ^_^

关注此空间!^_^

回答by Max Galkin

It's certainly a solution to your problem, but there is another problem, which somewhat undermines your intention to use "portable" XML format. Bad thing happens when you decide to change classes in the next version of your program and you need to support both formats of serialization -- the new one and the old one (because your clients still use thier old files/databases, or they connect to your server using old version of your product). But you can't use this serializator anymore, because you used

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

type.AssemblyQualifiedName

which looks like

看起来像

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

that is contains your assembly attributes and version...

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

Now if you try to change your assembly version, or you decide to sign it, this deserialization is not going to work...

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

回答by user2009677

Even better, using notation:

更好的是,使用符号:

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