What you need to know when you XmlSerialize classes derived from classes derived from CollectionBase
I started looking at the XmlSerializer when someone asked me to write a chapter for a book that he was working on. I wrote that chapter, started answering questions on the microsoft public newgroups, wrote another chapter and answered more question. Ultimately, the book was cancelled, but I'm still answering serialization questions in the microsoft.public.dotnet.xml newsgroup on news.microsoft.com. Some of the material also made it to www.topxml.com as a number of tutorials. If you are looking for an introduction to serializing classes from CollectionBase, please check there. This article documents one anomaly related to building a hierarchy of classes that derive from CollectionBase and Xml serialization.
So far I've managed to come up with a solution to most serialization problems. However, last week a co-worker of mine asked for some help with serializing collections. Easy, or so I thought, all he wanted was to serialize and deserialize a collection derived from CollectionBase ... and classes derived from that. Serialization goes just fine, but once you try to deserialize things are starting to get wicked. Take a look for yourself. Let's start with a custom collection and another class deriving from it(this is VB.NET since that particular client asked for it):
Public Class
Inherits CollectionBase
Public Sub New()
End Sub
Public Shadows Function Add(ByVal value As Object) As Integer
Return list.Add(value)
End Function
Default Overloads Property Item(ByVal index As Integer) As
Get
Return List.Item(index)
End Get
Set(ByVal Value As Object)
List.Item(index) = Value
End Set
End Property
End Class
Public Class FubaredCollection
Inherits CollectionBaseWithDefaultAccessor
Public Sub New()
End Sub
Public Shadows Function Add(ByVal item As Fubar) As Integer
Return List.Add(item)
End Function
Default Overloads Property Item(ByVal index As Integer) As Fubar
Get
Return CType(List.Item(index), Fubar)
End Get
Set(ByVal Value As Fubar)
List.Item(index) = Value
End Set
End Property
End Class
Serializing an instance of the derived class FubaredCollection with an XmlSerializer instantiated for the base typeCollectionBaseWithDefaultAccessor results in an Xml document like this. See if you can spot the problem:
...
Look at the root element and the element created for an object in the collection. You see that XmlSerializer recorded the type of the serialized object that was in referenced by the collection with an xsi:type=”Fubar” attribute. This is necessary because the XmlSerialzier cannot infer an object type to desrializate from the anyType element name. anyType stands for the type object. On the other hand, there is no xsi:type attribute on the root element ArrayOfAnyType, therefore the XmlSerializer will not deserialize a FubaredCollection. It will create a CollectionBaseWithDefaultAccessor, which is the base class of FubaredCollection.
This problem show its full ugliness when a class has a field defined as the base class, CollectionBaseWithDefaultAccessor, but carries an instance of a derived class.In our case we had a property definition like this:
Private m_Collection As CollectionBaseWithDefaultAccessor
Public Property Collection() As CollectionBaseWithDefaultAccessor
Get
Return m_Collection
End Get
Set(ByVal Value As CollectionBaseWithDefaultAccessor)
m_Collection = Value
End Set
End Property
which correctly serializes to:
...
In this case the XmlSerializer does have enough information to properly deserialize the Xml. The
Well, not quite! Deserializing the generated Xml results in an exception with this message:
System.InvalidOperationException:
There is an error in XML document (1, 187). ---> System.InvalidCastException: Specified cast is not valid.
at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReader1.Read1_CollectionWrapper(Boolean isNullable, Boolean checkType) in d:\Documents and Settings\csc\Local Settings\Temp\dysasxuv.0.cs:line 145
Since I had read about the great XmlSerializer debugging helper disclosed by Doug Purdy I was going to check what causes the exception:
The code generated by the XmlSerializer to deserialize Xml does not check the element name when it instantiates the collection object (on line 122). It simply instantiates an object of the defined base class type. Later on, it does check the element name, see the
Unfortunately, we didn't find a good work-around for it. The choice was not to go forward with the design of stronger typed collections derived from a weaker typed collection base class that derives from CollectionBase, but I did want to document the problem for the great guys working on the XmlSerializer (Doug, Matt, Stefan and everybody else). I hope we will get this fixed in Whidbey!