将派生类序列化/反序列化为基类 [英] Serialize/Deserialize derived class as base class

查看:66
本文介绍了将派生类序列化/反序列化为基类的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

例如我有以下类:

public abstract class Device
{
}

public class WindowsDevice: Device
{
}

public class AndroidDevice: Device
{
}

现在我想将 WindowsDevice 和 AndroidDevice 序列化/反序列化为 XML:

Now I want to serialize/deserialize WindowsDevice and AndroidDevice as 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();
        }
    }

这将产生以下输出:

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

但是现在我无法反序列化它,因为在我的应用程序中我不知道 XML 是 WindowsDevice 还是 AndroidDevice,所以我必须反序列化为 typeof(Device).但随后我会得到一个异常,即 XML 中的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). But then I will get an exception that "WindowsDevice" was unexpected in the XML.

我尝试了 XmlInclude 和 extraTypes,但没有成功.

I tried XmlInclude and extraTypes without any success.

我不明白的是,如果我有以下示例类:

What I dont understand is, that if I have the following sample class:

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

如果我序列化 SampleClass 并使用 XmlInclude 或 extraTypes 我完全得到我想要的:

and if I serialize SampleClass and use XmlInclude or extraTypes I exactly get what I want:

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

但我没有那个类,也没有设备列表.我只想序列化/反序列化 WindowsDevice 和 AndroidDevice 但在反序列化时我不知道它是 AndroidDevice 还是 WindowsDevice 所以我必须使用 typeof(Device) 并想获得正确的 sublass AndroidDevice 或 WindowsDevice,所以不是:

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></WindowsDevice>

我想要:

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

如何做到这一点?

推荐答案

您的问题是您在序列化和反序列化期间以不一致的方式构建您的 XmlSerializer.您需要使用相同的 Type 参数,特别是基本类型 typeof(Device).因此,我建议您将现有的完全通用的序列化方法替换为特定于 Device 的方法:

Your problem is that you are constructing your XmlSerializer inconsistently during serialization and deserialization. You need to construct it using the same Type argument in both cases, specifically the base type typeof(Device). Thus I'd suggest you replace your existing completely general serialization method with one specific for a 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);
        }
    }
}

然后,应用[XmlInclude(typeof(TDevice))]Device 所有可能的子类型:

Then, apply [XmlInclude(typeof(TDevice))] to Device for all possible subtypes:

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

那么这两种类型的设备现在都可以成功序列化和反序列化,同时保留它们的类型,因为 XmlSerializer 将包含一个 "xsi:type" 属性以明确指示类型:

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:

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

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

示例 fiddle.

更新

所以问题是,我使用 typeof(WindowsDevice) 而不是 typeof(Device) 进行序列化?

是的.

如果我必须使用 typeof(WindowsDevice),有没有什么可行的解决方案的想法?因为我有数百个类并且不想使用数百个不同的 XmlSerializer 初始化...

这更像是一个架构问题,而不是一个如何做的问题.一种可能性是引入一个自定义属性,您可以将其应用于一个类,以指示该类的任何子类型应始终序列化为属性基类型.还需要所有适当的 [XmlInclude(typeof(TDerivedType))] 属性:

This is more of an architectural question than a howto question. 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:

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

然后修改您的通用 XML 序列化代码以查找正在序列化的对象的类型层次结构以获取 [XmlBaseType] 属性,并(反)序列化为该类型:

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:

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

当然,这意味着如果您的代码试图反序列化它期望包含 WindowsDevice 的 XML,它实际上可能会返回一个 AndroidDevice,具体取决于 XML 的内容.

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.

示例 小提琴 #2.

这篇关于将派生类序列化/反序列化为基类的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆