【问题标题】:Serialize/Deserialize derived class as base class将派生类序列化/反序列化为基类
【发布时间】:2017-04-28 17:34:40
【问题描述】:

例如,我有以下课程:

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>

如何做到这一点?

【问题讨论】:

  • 开始标签没有告诉你你需要知道什么吗? 告诉您对象是什么。难道你不能只是阅读那个标签然后你就知道它是什么类型了吗?
  • 我有很多这样的类和一个通用的序列化/反序列化方法。我不想使用这种“肮脏”的解决方法。考虑重命名一个类,添加新类等。如果可能的话,我更喜欢一个干净的解决方案。如前所述,如果我使用 List,XmlSerializer 能够做我想做的事,所以我想知道如何在一个类的单个实例上使用该机制。
  • 我尝试了 XmlInclude 和 extraTypes 但没有成功。 - 你尝试了什么?我认为它应该有效。
  • @dbc: [XmlInclude(typeof(WindowsDevice)] public abstract class Device { } And: new XmlSerializer(o.GetType(), new Type[] { typeof(WindowsDevice})

标签: c# xml serialization


【解决方案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] 属性,并(反)序列化为该类型:

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 的内容。

示例fiddle #2

【讨论】:

  • 谢谢!所以问题是,我用 typeof(WindowsDevice) 而不是 typeof(Device) 序列化?如果我必须使用 typeof(WindowsDevice),有什么想法可以解决吗?因为我有数百个类并且不想使用数百个不同的 XmlSerializer 初始化......在运行时我有一个 WindowsDevice 实例,它应该传递给 XmlSerializer。我也不能总是使用传递的 Type 的 BaseType,因为有些类应该序列化为派生类(因为我所有的数百个类都是其他类的子类),有些作为它们的基类。
猜你喜欢
  • 1970-01-01
  • 2012-05-25
  • 1970-01-01
  • 1970-01-01
  • 2017-06-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多