How to properly XML-serialize collections with an extensible object hierarchy in .NET?-Collection of common programming errors
OdedScenario:
- I got 4 classes,
Configuration,Option,Option1andOption2 Option1andOption2inherit fromOptionConfigurationhas a property that is of typeList, in which I intend to store objects that descend fromOptionOptionis abstract, and will never be used to construct an actual instance.
The problem:
- When I simply annotate these objects and members with simple
[XmlType(...)]and[XmlElement(...)]attributes, the serialization of the objects in the collection ends up different than what I want - If I add the necessary
[XmlArray(...)]and[XmlArrayItem(...)]attributes, it looks like I want, but then I have to specify at compile-time a known list of item types, making extensibility impossible
Here’s an example LINQPad script that you can run:
void Main() { var Configuration = new Configuration(); Configuration.Options.Add(new Option1()); Configuration.Options.Add(new Option2()); var serializer = new XmlSerializer(typeof(Configuration), new Type[] { typeof(Option1), typeof(Option2), }); var ns = new XmlSerializerNamespaces(); ns.Add(string.Empty, string.Empty); using (var writer = new StringWriter()) { serializer.Serialize(writer, Configuration, ns); writer.ToString().Dump(); } } [XmlType("configuration")] public class Configuration { private readonly List _Options = new List(); [XmlElement("options")] public List Options { get { return _Options; } } [XmlArray("options2")] [XmlArrayItem(typeof(Option1))] [XmlArrayItem(typeof(Option2))] public List Options2 { get { return _Options; } } } public class Option { } [XmlType("option1")] public class Option1 : Option { } [XmlType("option2")] public class Option2 : Option { }When you run this, you get the following output:
Notice how the first property,
optionsis serialized quite different fromoptions2, but they have the same content.Basically, I’d like to have the serialized xml look like
options2, but without having to specify the allowed type of classes through attributes, since this will make extensibility impossible.If I have to provide them through something that can be altered at runtime, that’s fine, but hardcoded at compile-time is a no-go.
- I got 4 classes,
Marc GravellI think you are going to have to look at
XmlAttributeOverrides, which lets you supply the attributes at runtime. This is not trivial, but not mind-melting either. The biggest catch: when you use the constructor that acceptsXmlAttributeOverridesan assembly is generated every time, so be sure to cache and re-use theXmlSerializerinstance – otherwise you will leak assemblies (they can’t be collected).To implement you would have an
XmlAttributesinstance to which you tweakXmlArrayandXmlArrayItems, then useXmlAttributeOverrides.Add(Type,Member,XmlAttributes)to associate that with"Options2".