I have a class which can only be serialized with custom XML serialization which is to be used as a property of certain classes within a much larger system that uses basic serialization. I can’t convert the whole system to custom serialization because it’s huge and also may in the future contain third-party modules that use basic serialization.
My first question is whether this is allowed at all. I can’t see anything on MSDN that says you are not allowed to reference a custom-serialized object from a basic-serialised one, and it would seem like a serious restriction to the portability of code if you couldn’t. However an earlier answer on this site seems to suggest that you might not be able to (the poster says “You can't really mix and match serialization unfortunately; once you implement IXmlSerializable, you own everything”) although I think he is referring to mixing and matching within a class, which you clearly can't do. (See: Mixing custom and basic serialization?)
So assuming this is in fact allowed, my problem is that it fails whenever a class within the main system implements a List which contains two or more objects of the class that has custom serialization.
Interestingly, the failure only occurs on deserialization, and it only occurs when there is more than one reference to such an object. Even more intriguing, it fails in the same way even if the list appears further up the chain of dependencies (e.g. a list contains normal objects, which contain normal objects, which may contain objects that use custom serialization).
I have written a little test program that demonstrates the simplest case, as below.
So my questions are:
- Is it in fact allowed to reference custom-serialized objects from basic-serialized ones?
- If it is, am I doing something stupid?
- If not, is this a known bug?
NOTES ON THE TEST PROGRAM: The data class structure is very simple and consists of class BasicXml (which uses basic serialization) and class CustomXml (which uses custom serialization). BasicXml contains a list of CustomXml. The other class contains the test, which is self-standing. Just instantiate and run RunTests().
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Schema;
using System.Xml.Serialization;
namespace AdHocTests
{
[Serializable]
public class BasicXml
{
public List<CustomXml> TestList { get; set; }
}
[Serializable]
public class CustomXml : IXmlSerializable
{
public CustomXml() { }
public CustomXml(string name)
{
Name = name;
}
public string Name { get; set; }
public void ReadXml(XmlReader reader)
{
Name = reader.ReadString();
}
public void WriteXml(XmlWriter writer)
{
writer.WriteString(Name);
}
public XmlSchema GetSchema()
{
return null; // I have removed this code for clarity
}
}
public class MixedSerializationTest
{
public MixedSerializationTest()
{
_serializer.UnknownElement += UnknownElementHandler;
}
public void RunTests()
{
RunOneTest(makeItFail: false);
RunOneTest(makeItFail: true);
}
private void RunOneTest(bool makeItFail)
{
Debug.Write("\n\nRUNNING TEST THAT WILL " + (makeItFail ? "FAIL" : "PASS") + ":\n\n");
CustomXml c1 = new CustomXml("Hello");
CustomXml c2 = new CustomXml("World");
BasicXml b1 = new BasicXml
{
TestList = makeItFail ? new List<CustomXml> { c1, c2 } : new List<CustomXml> { c1 }
};
XElement xml1 = GetXmlFromObject(b1);
Debug.Write("Serialized XML:\n" + xml1.ToString() + "\n=====\n");
BasicXml b2 = GetObjectFromXml(xml1);
if (_cancelDeserialization) return;
XElement xml2 = GetXmlFromObject(b2);
Debug.Write("Reserialized XML:\n" + xml2.ToString() + "\n=====\n");
}
private XElement GetXmlFromObject(BasicXml obj)
{
using (StringWriter sw = new StringWriter())
{
using (XmlWriter xw = XmlWriter.Create(sw))
{
_serializer.Serialize(xw, obj);
return XElement.Parse(sw.ToString());
}
}
}
private BasicXml GetObjectFromXml(XElement xml)
{
using (StringReader sr = new StringReader(xml.ToString()))
{
XmlWriterSettings settings = new XmlWriterSettings();
using (XmlReader xr = XmlReader.Create(sr))
{
return (BasicXml)_serializer.Deserialize(xr);
}
}
}
private void UnknownElementHandler(object sender, XmlElementEventArgs e)
{
Debug.Write("\n*** Serializer threw an UnknownElement exception ***\n\n");
_cancelDeserialization = true;
}
private XmlSerializer _serializer = new XmlSerializer(typeof(BasicXml));
private bool _cancelDeserialization = false;
}
}