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

时间:2017-04-28 08:11:09

标签: c# xml serialization

例如,我有以下类:

public abstract class Device
{
}

public class WindowsDevice: Device
{
}

public class AndroidDevice: Device
{
}

现在我想将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();
        }
    }

这将产生以下输出:

<?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)。但是我会得到一个例外,“WindowsDevice”在XML中是意外的。

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

我不明白的是,如果我有以下样本类:

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

如果我序列化SampleClass并使用XmlInclude或extraTypes,我会得到我想要的东西:

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

但是我没有那个课,我没有设备列表。我只想序列化/反序列化WindowsDevice和AndroidDevice但是反序列化我不知道它是AndroidDevice还是WindowsDevice所以我必须使用typeof(Device)并且想要获得正确的子类AndroidDevice或WindowsDevice,所以代替:

<WindowsDevice></WindowsDevice>

我希望:

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

如何做到这一点?

1 个答案:

答案 0 :(得分:1)

您的问题是您在序列化和反序列化过程中构建XmlSerializer不一致。在这两种情况下,您都需要使用相同的Type参数构造它,特别是基类型typeof(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);
        }
    }
}

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

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

然后,两种类型的设备现在都可以成功序列化和反序列化,同时保留其类型,因为XmlSerializer将包含"xsi: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初始化......

这更像是一个建筑问题,而不是一个如何问题。一种可能性是引入一个custom attribute,您可以将其应用于类,以指示该类的任何子类型应始终序列化为属性基类型。还需要所有适当的[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
{
}

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

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

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

示例fiddle #2