The XML serialization in .NET allows polymorphic objects through the extraTypes[]
parameter of the XmlSerializer
constructor. It also allows customization of XML serialization for types that implement IXmlSerializable
.
However, I’m unable to combine these two features – as demonstrated in this minimal example:
using System;
using System.IO;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
namespace CsFoo
{
public class CustomSerializable : IXmlSerializable
{
public XmlSchema GetSchema() { return null; }
public void ReadXml(XmlReader xr) { }
public void WriteXml(XmlWriter xw) { }
}
class CsFoo
{
static void Main()
{
XmlSerializer xs = new XmlSerializer(
typeof(object),
new Type[] { typeof(CustomSerializable) });
xs.Serialize(new StringWriter(), new CustomSerializable());
}
}
The last line throws System.InvalidOperationException
with this message:
The type CsFoo.CustomSerializable may not be used in this context to use CsFoo.CustomSerializable as a parameter, return type, or member of a class or struct, the parameter, return type, or member must be declared as type CsFoo.CustomSerializable (it cannot be object). Objects of type CsFoo.CustomSerializable may not be used in un-typed collections, such as ArrayLists.
Wading through the dynamically generated XML assemblies, we ultimately come back to .NET standard library code by calling:
System.Xml.Serialization.XmlSerializationWriter.WriteTypedPrimitive(
String, String, Object, Boolean) : Void
In turn, this leads to:
protected Exception CreateUnknownTypeException(Type type)
{
if (typeof(IXmlSerializable).IsAssignableFrom(type))
{
return new InvalidOperationException(
Res.GetString("XmlInvalidSerializable",
new object[] { type.FullName }));
}
// Rest omitted...
Reflector shows that the XmlInvalidSerializable
resource corresponds with the string above – i.e., WriteTypedPrimitive
doesn’t like IXmlSerializable
.
If we generate a non-polymorphic serializer, like so:
XmlSerializer xs = new XmlSerializer(typeof(CustomSerializable));
.NET will generate a call to:
System.Xml.Serialization.XmlSerializationWriter.WriteSerializable(
IXmlSerializable, String, String, Boolean) : Void
This handles IXmlSerializable
properly. Does anybody know why .NET doesn’t use this function in the polymorphic case? Looking at the C# that the XML serializer generates, it appears to me this can be done quite easily. Here's some code I got from the XML serializer, with an untested solution:
void Write1_Object(string n, string ns, global::System.Object o,
bool isNullable, bool needType)
{
if ((object)o == null)
{
if (isNullable) WriteNullTagLiteral(n, ns);
return;
}
if (!needType)
{
System.Type t = o.GetType();
if (t == typeof(global::System.Object))
{
}
>>> patch begin <<<
+ else if (typeof(IXmlSerializable).IsAssignableFrom(t))
+ {
+ WriteSerializable((System.Xml.Serialization.IXmlSerializable)
((global::CsFoo.CustomSerializable)o),
+ @"CustomSerializable", @"", true, true);
+ }
>>> patch end <<<
else
{
WriteTypedPrimitive(n, ns, o, true);
return;
}
}
WriteStartElement(n, ns, o, false, null);
WriteEndElement(o);
}
Is this left out for technical reasons or just a feature limitation? Unsupported feature, or my idiocy? My intertubes Google skills fail me.
I did find some related questions here, with "C# Xml-Serializing a derived class using IXmlSerializable" being most relevant. It leads me to believe that it's simply not possible.
In that case, my current thought is to inject a default IXmlSerializable
implementation in the root base class. Then everything will be an IXmlSerializable
, and .NET won't complain. I can use Reflection.Emit to whip out the ReadXml
and WriteXml
bodies for each concrete types, generating XML that would look the same as it would if I used the library one.
Some people, when confronted with an XML serialization problem, think "I know, I'll use Reflection.Emit to generate code." Now they have two problems.
P.S. Note; I'm aware of alternatives to .NET's XML serialization, and know it has limitations. I also know that saving a POCO is vastly simpler than dealing with abstract data types. But I've got a pile of legacy code, and need support for existing XML schemas.
So while I appreciate replies that show how easy this is in SomeOtherXML
, YAML
, XAML
, ProtocolBuffers
, DataContract
, RandomJsonLibrary
, Thrift
, or your MorseCodeBasedSerializeToMp3
library - hey I might learn something -, what I'm hoping for is an XML serializer work-around, if not solution.