Reputation: 263
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.
Upvotes: 9
Views: 4752
Reputation: 705
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.
Upvotes: 1
Reputation: 4725
In order for IxmlSerializable to work, the class needs to have a null constructor.
Consider a base class
Upvotes: 0
Reputation: 161811
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.
Upvotes: 2