Shredding, coding, arguing etc.

Handling collections elegantly in C#

Interfaces vs Concrete types

27 July 2008

Shed a tear for the humble array that has been largely abstracted to a realm of computing where only the brave or stupid dare tread anymore.

The .NET framework offers a plethora of choice for your "collections". In fact, there are so many collections provided in the framework that I keep on stumbling upon collection classes I have never seen before.

Here is a sample of some of the collections available in the framework:

List of C# .NET collections

  • Normal array (i.e. string[])
  • Array
  • ArrayList
  • BindingList
  • BitArray
  • Collection
  • DataTable
  • DataView
  • Dictionary
  • HashSet
  • HashTable
  • HybridDictionary
  • KeyedCollection
  • KeyedByTypeCollection
  • LinkedList
  • List
  • ListDictionary
  • NameValueCollection
  • ObservableCollection
  • OrderedDictionary
  • Queue
  • Queue (generic)
  • ReadOnlyCollection
  • ReadOnlyObservableCollection
  • SortedList
  • SortedList (generic)
  • Stack
  • Stack (generic)
  • StringCollection
  • StringDictionary
  • SynchronizedCollection
  • SynchronizedKeyedCollection
  • SynchronizedReadOnlyCollection

There are plenty more classes buried within the framework that derive from these collections and are usually closely coupled with other classes. It sometimes seems that every, single class requires a corresponding Collection class - so we have AttributeCollection, ComponentCollection, HttpCookieCollection, ListSortDescriptionCollection etc.

Funnily enough, there are a tonne of collections, common amongst other programming languages, that are missing from the .NET framework too. If you really want to get crazy with collections, try out the NGenerics framework, which at the time of writing includes:

  • Bag
  • Graph
  • HashList
  • Heap
  • ObjectMatrix
  • PascalSet
  • CircularQueue
  • Vector2D
  • Vector3D
  • VectorN
  • PriorityQueue
  • SkipList
  • BinaryTree
  • BinarySearchTree
  • GeneralTree
  • RedBlackTree
  • SplayTree

Which collection should I use?

The majority of programmers out there probably never use more than a handful of the available collections, unless they are working in specialized fields or are particularly adventurous. What happen instead, is that programmers develop an affinity with their favourite programming patterns and do things the same way every time. The trusty and powerful List becomes ubiquitous and one rarely needs any further functionality, so why bother with all those other fancy classes?

The trouble with Collections

But what happens when you need to work with a framework that prefers different types of collections to all your code?

I recently had a bad experience upgrading my version of the SubSonic framework (a data access layer framework). Originally all their collections inherited from the generic List. But for some reason they decided to switch to inheriting from BindingList.

Ouch! Throughout my code I had been relying on the methods available within the List class, such as sorting, finding by a predicate etc. After upgrading SubSonic and compiling I was greeted with a list of more than 100 errors. This couldn't be fixed with a simple global find and replace either... this required severe old-school finger pumping.

Coding to interfaces... a solution?

At some point in a programmers career, there occurs a shift in his or her output, from code that never uses interfaces to code that use interfaces everywhere. Some people never reach this point, but for those who do, truly understanding interfaces occurs as a sort of epiphany - opening up a whole new world.

Evidence of such a shift can be uncovered in the .NET framework itself - with each successive iteration of the framework, interfaces become more prevalant and more useful.

If you are still skeptical as to the benefits of interfaces, I highly recommend reading up on design patterns in general. The Head First book is a great place to start.

The advantage of coding to interfaces starts to become clear in the case of my troubles with SubSonic and its collections. Instead of all my methods requesting concrete classes, such as Lists, as their parameters, I should have had them request interfaces, such as the ICollection.

   public void DoSomething(ICollection<Foo> collection)
   {
      // Lend me your ICollections
   }

Because the majority of the collections in the .NET framework inherit from ICollection, or at the least IEnumerable, theoretically life is now easier. If the instantiated concrete classes change, as long as those classes still implement ICollection, everything is peachy. Even if the classes don't implement ICollection, a variety of sneaky design patterns can be wielded to brutally solve the problem - Adapters, Decorators and so forth.

Limitations of IList

Unfortunately, certain limitations of C# and .NET framework mean that coding to interfaces is not always an optimal solution.

No Covariance

Covariance of generic collections is not supported in C#. Bummer.

Imagine I have a list of strings (List<string>). One would think that I could pass my string list to a method's parameter that required a list of objects (List<object>), without having to engage adapters or helpers or anything else. Unfortunately a clean pass is impossible.

   /* This will never happen */
   
   public void LegalizeMarijuana()
   {
       List<MarijuanaStrain> strains = LoadStrains();
       Legalize(strains);
   }
   
   public List<LegalDrug> Legalize(IList<IllegalDrug> drugs)
   {
       // etc
   }

A variety of adapter classes can be found out on the net to help with this, but inevitably you end up with more bloated code:

   
   public void LegalizeMarijuana()
   {
       List<MarijuanaStrain> strains = LoadStrains();
       /* Adapt to a list of illegal drugs */
       Legalize(new ListAdapter<MarijuanaStrain,IllegalDrug>(strains));
   }
   
   public List<LegalDrug> Legalize(IList<IllegalDrug> drugs)
   {
       // etc
   }

Despite the bloat, it is lucky that the Legalize method accepted an IList rather than a concrete List, or else we would struggle to use the adapter class at all.

Here are a set of adapter classes for you to download (credits go to the Umbrella framework).

Update: Apparently C# 4.0 will be supporting covariance and contravariance - bring on 2010!

Not all Lists are ILists

Some of the collections in the .NET framework don't implement all the interfaces you would expect them to.

Thankfully the ICollection has been used fairly thoroughly, so we can at least code to ICollection, even if IList is not implemented.

But I like my List!

The next problem, is that there is a whole bunch of useful functionality available in the List class. If you start accepting ILists everywhere, you are going to find yourself unable to sort, find, remove a range etc. This is a real pity, because these are all very convenient methods and save hours of coding.

If you're sticking with ICollection then you are in for even more tears because it doesn't even have methods for adding, indexing or removing!

As far as I know, there are two options here to try and regain some lost ground:

  1. Use static helper classes (much better with C# 3's extension methods)
  2. Roll your own IFancyList interface(s), then combine it with decorator classes that extended ICollections with sorting, searching etc.

The first option, creating some extension methods, is probably easiest and will get you out of many sticky situations.

Conclusion

Despite the problems, I still think that it is wise to accept interfaces rather than concrete classes. With careful use of adapters and C# 3 extension methods, the situation is not too bad.

If some hard and fast rules were needed to add to your company policy and enforce upon your underlings, perhaps something along these lines would do the trick:

  1. For all public properties and method parameters, accept the least restrictive interface
  2. For all custom collections, implement the most restrictive interface possible

This means that if a custom collection is in the slightest bit list-like, it should implement IList whenever possible. Conversly, all your methods should accept ICollections, even if this means re-implementing some functionality through static extension classes or decorators.

Comments

 
   
Stupidity filter

Unfortunately, the internet is rife with automaton zombies that fill up blog threads with rubbish. There are also computer scripts that do this too. Please answer the following question.

Waht is the nmae of the plnaet clsoset to the cneter of the sloar ssyetm?    
  •  
  • del.icio.us  del.icio.us
  • reddit  reddit
  • StumbleUpon  StumbleUpon