Mark Wilson I am the creator of TopXML. I am available for international and local (Australia) contracts. I am a Solution Architect/Business Analyst. I have worked in IT in several countries (NZ, Australia, South Africa, UK) building and training teams for government and very large non-governmental organizations. I am ex-Microsoft Consulting Services. I wrote the first book on Microsoft XML published in 2000 called XML Programming with VB and ASP. Most recently I have been building tools for the SEO industry. Ask me for a 37 point SEO health-checkup for your website.
The previous section showed us how to annotate source files with
metadata attributes to customize how arrays are mapped to XML. Now we will see
if our knowledge transfers to collection classes. These classes are very similar
to arrays but don’t require a fixed size, can hold unrelated types and are
optimized for different usage scenarios. The .NET Framework provides a number
of collection classes ready to use in the System.Collections namespace, for example an ArrayList, a Dictionary
or a Hashtable just to name a few.
The XmlSerializer can process all collections that implement one
of the .NET framework’s collection interfaces: IEnumerable or ICollection. All
collections provided by the .NET framework implement these interfaces, so we
can serialize or deserialize these collections without much additional work.
All of the framework’s collections are weakly typed, so we have to declare the
types stored inside a collection before the XmlSerializer can correctly process
it. Declaring types inside a collection works just like declaring types inside
an array, which we learned in section 9.5.1.
Customizing how a collection is mapped to XML is very much like
customizing an array. Attaching the XmlArray attribute and the XmlArrayItem attribute to a
collection class has the same effect as attaching them to arrays. Let’s confirm
this and replace the Vehicle array in the ParkingLot class from the previous
example with the more flexible System.Collections.ArrayList.
2
Listing 9.7 Use of
XmlArray attributes with a collection class
Even though we are
now using an ArrayList instead of an array the XML
representation of the new ParkingLot class has not
changed.
This may seem odd to those of us already familiar with the ArrayList collection. The ArrayList
exposes seven public properties; none of these properties
is serialized. The public properties
are considered as auxiliary information only, so whenever the Serialize()
method detects an object that implements ICollection
or IEnumerable it will ignore
the object’s public properties. Instead it uses the interfaces to serialize all
the items inside the collections. This again reminds us that the intended use
of the XmlSerializer is in data-driven environments
where the data and its XML representation are the primary focus, not a 100%
accurate snapshot of an object.
In many cases we might like to extend the collection classes
provided by the framework. Maybe we need to ensure that only objects of certain
types are stored in the collection or we need a different sorting algorithm.
Either way, custom collection classes will de-/serialize properly as long as
they implement IEnumerable or ICollection and, of course, we supply enough
information about the types stored inside the collection. Keep in mind that
only the items that can be accessed through the interfaces are serialized,
public properties and fields are not, unless they return a class that itself
implements ICollection.
There is another caveat when you implement your own container
classes: The implementation of the XmlSerializer requires the collection to have a default
accessor, even though ICollection does not require it. In VB.Net a default
accessor is implemented as an Item property with a single parameter of type
Integer. In C# it is implemented as an indexer. The syntax for an indexer
resembles a read-only property, but it uses the square brackets around the
parameter. Listing 9.8 gives an example
for a strongly-typed collection based on an ArrayList with a default accessor:
3
Listing 9.8 Custom
collection with a default indexer.
public class CarArray : ICollection
{
public CarArray() { Cars
= new ArrayList(); }
// ICollection public
properties
public int Count { get {
return Cars.Count; } }
public bool
IsSynchronized { get { return Cars.IsSynchronized; } }
public object SyncRoot {
get { return Cars.SyncRoot; } }
// ICollection public
method
public void CopyTo(
Array a, int i ){ Cars.CopyTo( a, i ); }
// IEnumerable
public IEnumerator
GetEnumerator()
{
return
Cars.GetEnumerator();
}
// the default indexer
public Car this[int
i] |#1
{ |
get |
{ |
return (Car)
Cars[i]; |
} |
} |
private ArrayList
Cars; |#2
// only add Cars and
derived classes to the arraylist
public void Add( Car c
){ Cars.Add( c ); }
}
(annotation)
<#1 The default indexer is used to enumerate through the elements of an
array or collection. The square brackets denote an indexer in C#.>
(annotation)
<#2 The field to store the collection data has to be private. Because the
ArrayList implements ICollection. >
NOTE: No metadata attributes were attached to the ArrayList
member in the example above. The XmlSerializer can process objects of the type
returned by the default accessor without attaching any additional attributes.
Any other types need to be declared through an XmlInclude, an XmlElement or an XmlArrayItem
attribute