XML Serialization Continued: Arrays

December 30, 2014 - .NET, C#, Programming

I’ve finally managed to get my XML serialization library to a point where I’ve been able to start implementing it within another program. My first target is BASeBlock, though the aim is to also use it with new projects in preference to both Binary Serialization as well as manually constructing and reading XML documents or other file formats. By supporting both the interface-based approach, where classes themselves can understand and implement their serialization, as well as having the ability to define “Providers” which can do so separate from the class implementation, the library should be usable for those with various design goals and preferences in terms of class design.

In implementing and adding “support” for the IXmlPersistable interface to BASeBlock, however, I hit an interesting snag- Arrays. Many classes serialize Arrays, which evidently support ISerializable and thus work with that, but my XML persistence library doesn’t know anything about Arrays. Worse still, in the particular instance I encountered, the array was actually an array of arrays ( float[][]), so converting to a List and back while possible would be a huge mess.

I decided that if something was worth doing, it was worth doing properly, and support for easily saving and loading arrays via the “Helper” class was important. Further still, it would need to act recursively and support multidimensional arrays.

The “StandardHelper” class I created within the library is effectively a class that provides some of the gruntwork for turning a class into it’s XElement serialization and back again. For array support, the idea is straight forward- create two methods for reading and writing a System.Array, and then edit SaveElement to properly recognize when the Type parameter is an Array and use that method appropriately.

The strategy I chose for saving the data tends to cause the XML data to be quite large in comparison but it also makes it a bit easier to manipulate by hand. Effectively the Node created will have an attribute specifying the Rank of the Array (the number of dimensions) as well as an attribute specifying the dimension bounds, as a comma-delimited string attribute. The actual Element data is saved within nested elements that have an attribute that specify the indices of the element, and the actual contents of that node will be the result of saving the actual element type parameter to a XElement, which means that any type supported by the library’s “SaveElement” method will work, which effectively means that it automatically supports arrays of arrays such as float[][] or float[,][] and so on. Interestingly the part I found most difficult was in traversing all the array elements. I ended up using an approach that effectively treats each dimension as a place value and increments through all of them one by one via a “carry-bit”, and then when all bounds are the upper bound, it has completed.

For any save, we obviously want to be able to read it back in. In this instance I took the approach of having it return a System.Array, with the idea that the calling code ought to know what the actual type of the array is to cast it appropriately. It does however accept a Type Parameter which specifies the actual element type of the Array, as this is useful for the task of constructing the array. This data could feasibly have been stored as part of the metadata of the actual Array container XELement along with dimensions and upper bound, but it seems, again, reasonable to expect that the calling code will have some idea what the appropriate data type is.

Both of these methods currently lack any sophisticated Error handling or Exception throwing as one would more reasonably expect from a library; for example, trying to read an int saved to an XElement and read that XElement via ReadArray would indubitably throw an Exception related to missing Attributes. Such mismatched data most likely should result in a strongly-typed Exception such as a “MismatchedElementDataException” that can be traced to the library itself.

The most interesting endeavour is the usage of Reflection. Though it is a bit goofy to use sometimes it really does allow for powerful constructs to be used, and I particularly find it powerful when dealing with Generic types, as one can construct Generic method definitions or even Generic Class types with the appropriate information and then return data fluently to callers. In this instance it is primarily used to construct a generic method reference to the SaveElement with T being the type of the elements in the input array.

Again, the main goal of this library is to effectively serve as a reasonable alternative to the existing Serialization framework. I am using BASeBlock as a testbed for experimentation currently, but the goal is to provide a safe and less “black-box” alternative to the built-in IFormatter and BinaryFormatter serialization features of .NET and provide XML capabilities through a similar approach, as I found the class-design constraints of using the existing XML Serialization to be limiting.

Have something to say about this post? Comment!