views:

1065

answers:

2

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.

+2  A: 

I was able to reproduce your problem, when using object:

XmlSerializer xs = new XmlSerializer(
    typeof(object),
    new Type[] { typeof(CustomSerializable) });

However, I then created a derived class of CustomSerializable:

public class CustomSerializableDerived : CustomSerializable
{
}

And tried to serialize it:

XmlSerializer xs = new XmlSerializer(
    typeof(CustomSerializable),
    new Type[] { typeof(CustomSerializableDerived) });

This worked.

So, it would appear that the problem is restricted to the case where you specify "object" as the type to serialize, but not if you specify a concrete base type.

I'll do some more research on this in the morning.

John Saunders
A: 

In order for IxmlSerializable to work, the class needs to have a null constructor.

Consider a base class

  • MyBaseXmlClass:IXmlSerializable
    -- implement GetSchema
  • MyXmlClass:MyBaseXmlClass
    -- ReadXml
    • WriteXml
david valentine
-1: His does have a default constructor; you could use actual code instead of just text to illustrate your point.
John Saunders