Back again, and as promised...

Subclassing Generic Collections

The framework provides three basic collections that are intended for derivation.  They are located in System.Collections.ObjectModel:

They are all intended to provide base implementations of the following interfaces:

  • IList, IList<T>
  • ICollection, ICollection<T>
  • IEnumerable, IEnumerable<T>

Collection<T> stores it's data internally as IList<T> (and also takes IList<T> in one of the constructors).  The methods that actually modify the internal IList<T> are virtual (and protected):

  • ClearItems()
  • InsertItem()
  • RemoveItem()
  • SetItem()

This class is intended for when you wish to handle all modifications done to the collection.

KeyedCollection<TKey, TItem> is an abstract class that derives from Collection<T>.  It is meant to store values where the key is imbedded within the value.  It overrides the virtual methods from Collection<T> and adds an abstract GetKeyForItem().

ReadOnlyCollection<T> supports the same interfaces as Collection<T> and also stores it's data as an IList<T>.  There are no virtual methods and all of the iterface method implementations that would normally modify the stored IList<T> are private and throw if called.  (Add(), Clear(), Remove(), etc.)  The only time the collection is modified is when it is constructed.  (i.e., you pass in an existing IList<T>)

Now all of the other collections are not sealed, so you can derive from any of them.  They do not, however, have any methods marked as virtual (other than GetObjectData() and OnSerialization()).  Why would you want to override them if you cannot change/intercept their behavior?  There are two reasons I've come up with so far:

  • Attaching utility methods.
  • Readability.

Lets say you had a path with a number of points.  You could create a List<Point> to represent said path.  This works, but say you want to create a list of your paths?  This turns into List<List<Point>>.  Hmm.  Not nice.  In some cases you might even end up with List<List<List<Point>>>.   Ugh.  Since List<Point> represents a specific object we could subclass List<T> as follows:

public class Path : List<Point>
{
   // Need a constructor (and you'd likely want one that takes IEnumerable<T>):
   public Path() : base() {};
}

Now we can say List<Path>.  Much clearer- the sort of code you'd want to read/maintain.  In addition we can add useful utility methods to our new Path class such as IsClosed() or IsValid().

That's about it for subclassing for now.  Lets take a look at something I hinted at earlier...

System.Array

Ends up that most of the useful methods in List<T> have static equivalents in System.Array.  Very useful.  Looking at a previous example we could have had:

public void SumUpMeaningDemo()
{
   int[] magicNumbers = { 7, 13, 1000, 42, 9 };
   int total = 0;

   Array.ForEach<int>(newNumbers, delegate(int number) { total += number; });
}

Since these are static methods you need to specifiy the type of the array and which array you're working on.  Other than that things work pretty much the same.  Happy days that the framework team added this support.

Wait a Minute...

Those that read ahead may have noticed that only List<T> and Array have methods that utilize the system delegate types.  It's true.  Before you get too depressed about this, in a future post I'll discuss a project that gives you loads of functionality for all of the generic interface types (IList<T>, IEnumerable<T>, ICollection<T>).  It is an amazingly useful package called PowerCollections.

Till then...