Thursday, January 4, 2007

.NET Xml Serialization with complex collections

So, the other day, I was doing some XML serialization work in .NET and I thought to myself, I need some more complex collection members in this XML heirarchy but I'd still like them to be serialized. What do I mean? Well, the default type of collections used by the XML serialization in .NET (if you use xsd.exe to generate the code for your serialization classes) is a strongly typed array. For instance, let's say I have XML that looks like the following:

<root>
<entry id="1" value="hello">
<entry id="2" value="there">
</root>

Now, let's say I use xsd.exe to generate my code (first I run xsd to infer the schema, then I run it to generate classes for this schema). I'll get two classes - one of them will be called "root" and the other "rootEntry". The class named "root" will get a member called Items declared as

private rootEntry[] itemsField;

/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute("entry",
Form=System.Xml.Schema.XmlSchemaForm.Unqualified)]
public rootEntry[] Items {
get {
return this.itemsField;
}
set {
this.itemsField = value;
}
}

My issue was this - I wanted to use my classes to generate XML, not just read it. In the case of generating XML using these classes, it's a bit of a pain to have to build the array first, before attaching it to the class. It'd be much nicer if I could just have a List<rootentry> as my collection type.

Well, it turns out, that having a List<rootentry> as the collection type of the Items property is quite easy! The .NET framework even knows how to construct it for you, so you don't have to construct it by default. All you need to do is change the declarations above from rootEntry[] to List<rootentry> (include System.Collections.Generic in your using list, of course).

All that is fine, but I would like to use a custom IList<rootentry> implementation so that my collection can have other niceties (like providing notifications when one of the entries has changed in value, etc.), so I want to use my own collection type. At first, I thought I could just derive from List<rootentry> to build my collection type, but unfortunely, most of the stuff in List is not virtual, so it can't be overridden. Therefore, I decided to try building a list object that implements IList<rootentry> and see what happens.

My class was declared like this:


public class OwnedList<TElem, TOwner>: IList<TElem> {
private List<TElem> _Items;
private TOwner _Owner;

public OwnedList(TOwner Owner)
{
this._Items = new List<TElem>();
this._Owner = Owner;
}

...
}

Then, I used it in the property and field for root like:


private OwnedList<rootEntry, root> itemsField;

// constructor added so that itemsField is not
// NULL anymore when the serializer or caller
// gets the object.
public root() {
this.itemsField
= new OwnedList<rootEntry, root>(this);
}

/// <remarks>
[System.Xml.Serialization.XmlElementAttribute("entry",
Form=System.Xml.Schema.XmlSchemaForm.Unqualified)]
public OwnedList<rootEntry, root> Items {
get {
return this.itemsField;
}
}

Notice, I removed the "set" accessor for the property and added a constructor for root that creates the collection. This is necessary so that my collection gets the appropriate "Owner" when it is created. Now, I can write code to use normal IList operations on my Items collection when building my list of items, rather than having to build the array before adding it to the object. Xml serialization works and gives the identical results to before!

Happy coding!

No comments: