简体   繁体   中英

Polymorphic Types and IXmlSerializable

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 – ie, 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.


PS 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.

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.

First of all posters statement

The XML serialization in .NET allows polymorphic objects through the extraTypes[] parameter of the XmlSerializer constructor.

is misleading. According to MSDN extratypes is used for:

If a property or field returns an array , the extraTypes parameter specifies objects that can be inserted into the array.

Meaning that if somewhere in your serialized object graph polymorphic objects are returned via array, they can be processed.

While I haven't really found a solution how to serialize a polymorphic type as root XML object, I was able to serialize polymorphic types found in object graph using either standard XML serializer or IXmlSerializable. See below for my solution:

using System.IO;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;

namespace CsFoo
{
  public class Foo
  {
    [XmlElement("BarStandard", typeof(BarStandardSerializable))]
    [XmlElement("BarCustom", typeof(BarCustomSerializable))]
    public Bar BarProperty { get; set; }
  }

  public abstract class Bar
  { }

  public class BarStandardSerializable : Bar
  { }

  public class BarCustomSerializable : Bar, IXmlSerializable
  {
    public XmlSchema GetSchema() { return null; }
    public void ReadXml(XmlReader xr) { }
    public void WriteXml(XmlWriter xw) { }
  }

  class CsFoo
  {
    static void Main()
    {
        StringWriter sw = new StringWriter();
        Foo f1 = new Foo() { BarProperty = new BarCustomSerializable() };
        XmlSerializer xs = new XmlSerializer(typeof(Foo));

        xs.Serialize(sw, f1);
        StringReader sr= new StringReader(sw.ToString());
        Foo f2 = (Foo)xs.Deserialize(sr);
    }
  }
}

Note that using either

XmlSerializer xs = new XmlSerializer(typeof(Foo), 
            new Type[] { typeof(BarStandardSerializable),
            typeof(BarCustomSerializable)});

or

[XmlInclude(typeof(BarCustomSerializable))]
[XmlInclude(typeof(BarStandardSerializable))]
public abstract class Bar
{ }

without XmlElement defined, would cause the code to fail at serialization.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM