简体   繁体   English

序列化/反序列化派生类为基类

[英]Serialize/Deserialize derived class as base class

For example I have the following classes: 例如,我有以下课程:

public abstract class Device
{
}

public class WindowsDevice: Device
{
}

public class AndroidDevice: Device
{
}

Now I want to serialize/deserialize WindowsDevice and AndroidDevice as XML: 现在,我想将WindowsDevice和AndroidDevice序列化/反序列化为XML:

public static string Serialize(object o, Type[] additionalTypes = null)
    {
        var serializer = new XmlSerializer(o.GetType(), additionalTypes);

        using (var stringWriter = new StringWriterWithEncoding(Encoding.UTF8))
        {
            serializer.Serialize(stringWriter, o);
            return stringWriter.ToString();
        }
    }

This will produce the following output: 这将产生以下输出:

<?xml version="1.0" encoding="utf-8"?>
<WindowsDevice xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">

</WindowsDevice>

But now I am unable to deserialize this, because in my application I don't know if the XML is WindowsDevice or AndroidDevice, so I have to deserialize as typeof(Device). 但是现在我无法反序列化,因为在我的应用程序中我不知道XML是WindowsDevice还是AndroidDevice,因此我必须反序列化为typeof(Device)。 But then I will get an exception that "WindowsDevice" was unexpected in the XML. 但是然后我会得到一个例外,即XML中的“ WindowsDevice”是意外的。

I tried XmlInclude and extraTypes without any success. 我尝试了XmlInclude和extraTypes,但没有成功。

What I dont understand is, that if I have the following sample class: 我不明白的是,如果我有以下示例类:

public class SampleClass
{
    public List<Device> Devices {get;set}
}

and if I serialize SampleClass and use XmlInclude or extraTypes I exactly get what I want: 如果我序列化SampleClass并使用XmlInclude或extraTypes,我将得到我想要的东西:

<Devices>
<Device xsi:type="WindowsDevice"></Device>
</Devices>

But I don't have that class and I don't have a list of Devices. 但是我没有该课程,也没有设备列表。 I only want to serialize/deserialize WindowsDevice and AndroidDevice but on Deserialize I don't know whether it is AndroidDevice or WindowsDevice so I have to use typeof(Device) and want to get the correct sublass AndroidDevice or WindowsDevice, so instead of: 我只想序列化/反序列化WindowsDevice和AndroidDevice,但是在反序列化时,我不知道它是AndroidDevice还是WindowsDevice,因此我必须使用typeof(Device)并想要获取正确的子类AndroidDevice或WindowsDevice,所以而不是:

<WindowsDevice></WindowsDevice>

I want to have: 我希望有:

<Device xsi:type="WindowsDevice"></Device>

How can this be done? 如何才能做到这一点?

Your problem is that you are constructing your XmlSerializer inconsistently during serialization and deserialization. 您的问题是在序列化和反序列化过程中不一致地构造XmlSerializer You need to construct it using the same Type argument in both cases, specifically the base type typeof(Device) . 在两种情况下,都需要使用相同的Type参数构造它,特别是基本类型typeof(Device) Thus I'd suggest you replace your existing completely general serialization method with one specific for a Device : 因此,我建议您使用特定于Device方法替换现有的完全通用的序列化方法:

public static class DeviceExtensions
{
    public static string SerializeDevice<TDevice>(this TDevice o) where TDevice : Device
    {
        // Ensure that [XmlInclude(typeof(TDevice))] is present on Device.
        // (Included for clarity -- actually XmlSerializer will make a similar check.)
        if (!typeof(Device).GetCustomAttributes<XmlIncludeAttribute>().Any(a => a.Type == o.GetType()))
        {
            throw new InvalidOperationException("Unknown device type " + o.GetType());
        }
        var serializer = new XmlSerializer(typeof(Device)); // Serialize as the base class
        using (var stringWriter = new StringWriterWithEncoding(Encoding.UTF8))
        {
            serializer.Serialize(stringWriter, o);
            return stringWriter.ToString();
        }
    }

    public static Device DeserializeDevice(this string xml)
    {
        var serial = new XmlSerializer(typeof(Device));
        using (var reader = new StringReader(xml))
        {
            return (Device)serial.Deserialize(reader);
        }
    }
}

Then, apply [XmlInclude(typeof(TDevice))] to Device for all possible subtypes: 然后,将[XmlInclude(typeof(TDevice))]应用于所有可能的子类型的Device

[XmlInclude(typeof(WindowsDevice))]
[XmlInclude(typeof(AndroidDevice))]
public abstract class Device
{
}

Then both types of devices can now be serialized and deserialized successfully while retaining their type, because XmlSerializer will include an "xsi:type" attribute to explicitly indicate the type: 然后,这两种类型的设备现在都可以成功进行序列化和反序列化,同时保留它们的类型,因为XmlSerializer将包含一个"xsi:type"属性来明确指示类型:

<Device xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:type="WindowsDevice" />

Or 要么

<Device xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:type="AndroidDevice" />

Sample fiddle . 样品提琴

Updates 更新

So the issue was, that I serialized with typeof(WindowsDevice) instead of typeof(Device)? 所以问题是,我使用typeof(WindowsDevice)而不是typeof(Device)进行了序列化?

Yes. 是。

Any ideas for a solution which will work, if I have to use typeof(WindowsDevice)? 如果必须使用typeof(WindowsDevice),对可以解决问题的解决方案有什么想法吗? Cause I have hundreds of classes and don't want to use hundreds of different XmlSerializer initializations... 因为我有数百个类,并且不想使用数百个不同的XmlSerializer初始化...

This is more of an architectural question than a howto question. 这不仅仅是一个架构问题,而不是一个howto问题。 One possibility would be to introduce a custom attribute that you can apply to a class to indicate that any subtypes of that class should always be serialized as the attributed base type. 一种可能是引入自定义属性 ,您可以将其应用于类,以指示该类的任何子类型都应始终序列化为属性基类型。 All appropriate [XmlInclude(typeof(TDerivedType))] attributes will also be required: 还需要所有适当的[XmlInclude(typeof(TDerivedType))]属性:

[System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public class XmlBaseTypeAttribute : System.Attribute
{
}   

[XmlInclude(typeof(WindowsDevice))]
[XmlInclude(typeof(AndroidDevice))]
[XmlBaseType]
public abstract class Device
{
}

Then modify your universal XML serialization code to look up the type hierarchy of the object being serialized for an [XmlBaseType] attribute, and (de)serialize as that type: 然后修改您的通用XML序列化代码,以为[XmlBaseType]属性[XmlBaseType]要序列化的对象的类型层次结构,并(反)序列化为该类型:

public static class XmlExtensions
{
    static Type GetSerializedType(this Type type)
    {
        var serializedType = type.BaseTypesAndSelf().Where(t => Attribute.IsDefined(t, typeof(XmlBaseTypeAttribute))).SingleOrDefault();
        if (serializedType != null)
        {
            // Ensure that [XmlInclude(typeof(TDerived))] is present on the base type
            // (Included for clarity -- actually XmlSerializer will make a similar check.)
            if (!serializedType.GetCustomAttributes<XmlIncludeAttribute>().Any(a => a.Type == type))
            {
                throw new InvalidOperationException(string.Format("Unknown subtype {0} of type {1}", type, serializedType));
            }
        }
        return serializedType ?? type;
    }

    public static string Serialize(this object o)
    {
        var serializer = new XmlSerializer(o.GetType().GetSerializedType());
        using (var stringWriter = new StringWriterWithEncoding(Encoding.UTF8))
        {
            serializer.Serialize(stringWriter, o);
            return stringWriter.ToString();
        }
    }

    public static T Deserialize<T>(this string xml)
    {
        var serial = new XmlSerializer(typeof(T).GetSerializedType());
        using (var reader = new StringReader(xml))
        {
            return (T)serial.Deserialize(reader);
        }
    }
}

Of course this means that if your code tries to deserialize XML it expects to contain a WindowsDevice , it might actually get back an AndroidDevice depending upon the contents of the XML. 当然,这意味着如果您的代码尝试反序列化XML,则它期望包含WindowsDevice ,则实际上可能会取回AndroidDevice具体取决于XML的内容。

Sample fiddle #2 . 样本小提琴2

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM