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);
}
}
}
Then, apply [XmlInclude(typeof(TDevice))]
to Device
for all possible subtypes:
[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:
<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)?
Yes.
Any ideas for a solution which will work, if I have to use typeof(WindowsDevice)? Cause I have hundreds of classes and don't want to use hundreds of different XmlSerializer initializations...
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
{
}
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);
}
}
}
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.
Sample fiddle #2.