Thursday, June 26, 2008

Implementing a .NET SortedKeyedCollection

I've been looking for a solution for this one for a while. The .NET Framework v2 provides a generic KeyedCollection<TKey, TItem>, that stores objects using a key which is a property on the object being stored, which is a fantastic concept. However, the objects are stored in a standard dictionary, in the order in which they were inserted, and there's no provision in the class for sorting the collection.

So I turned to the web for a solution - surely, someone somewhere must have tried to do the same thing as me. As it turns out, they had. This wasn't the only such example, but unfortunately, the advice given was always the same - add a Sort method, which is clunky, and forces the implementor to make an extra step to get their collection sorted; or use SortedList<TKey, TItem> or SortedDictionary<TKey, TItem>. These collections are full featured, containing all sorts of helpful methods and properties. I see similar arguments made when suggesting people use List<T> for pretty much any collection. All well and good for 90% of developers, but what do you do about the 10% who are API developers?

The reason why these classes are not so good for API developers is that these helpful classes are not easily extended - few, if any, of the methods or properties are virtual. Microsoft's own recommendation for API developers and developers of third-party libraries is that they use the Collection<T>, KeyedCollection<TKey, TItem>, and ReadOnlyCollection<T>, as these classes deliberately provide virtual methods which can be overridden to provide customisation.

So, here is my implementation of a SortedKeyedCollection<TKey, TItem>, which extends the KeyedCollection and sorts the objects at the point at which they are inserted. I'm adding this code to Google Code, more as a safe repository than to start an open source project - the project's just a shell at the moment, seeing as I can't get an SVN connection to it from work.

   1:  using System.Collections.Generic;
   2:  using System.Collections.ObjectModel;
   4:  public abstract class SortedKeyedCollection<TKey, TItem> : KeyedCollection<TKey, TItem>
   5:  {
   6:      protected virtual IComparer<TKey> KeyComparer
   7:      {
   8:          get 
   9:          {
  10:              return Comparer<TKey>.Default;
  11:          }
  12:      }
  14:      protected override void InsertItem(int index, TItem item)
  15:      {
  16:          int insertIndex = index;
  18:          for (int i = 0; i < Count; i++)
  19:          {
  20:              TItem retrievedItem = this[i];
  21:              if (KeyComparer.Compare(GetKeyForItem(item), GetKeyForItem(retrievedItem)) < 0)
  22:              {
  23:                  insertIndex = i;
  24:                  break;
  25:              }
  26:          }
  28:          base.InsertItem(insertIndex, item);
  29:      }
  30:  }

The InsertItem override does most of the work - if you're adding a new object, it intercepts that call, and works out the index that you should be inserting the object, based on its key. By default, it uses the Comparer<TKey>.Default, so string keys are compared using the default (case-insensitive) comparer, and other primitives such as ints and decimals are compared in a similar fashion. By providing a virtual property KeyComparer, I've allowed the user of the class to provide their own IComparer<TKey>, should they want to use a specific comparer, for instance if the key is a custom class.

Monday, June 23, 2008

Today's forecast - cold and sunny

No, not Sydney; and with a maximum temperature of -32 °C and a minimum of -80 °C, it's not even Canberra. Courtesy of the Canadian Space Agency, we can now get the daily forecast for Mars. Best wrap up warm.

Thursday, June 19, 2008

Tip - Documenting generics in .NET

If, like me, you're nuts about documenting all your classes, methods and properties so that NDoc or Sandcastle will generate nicely formatted API documentation for you, then you may have found that when it comes to documenting code that includes generics in its signature causes an error. This is because the angled brackets in the generics syntax gets confused with the angled brackets that are the nature of XML.

You can get around this by using the appropriate entity objects, but it's not particularly pretty to look at:

using System.Collections.Generic; public class MyProgram { /// <summary> /// Processes the contents of a <see cref="List&lt;T&gt;" />. /// </summary> /// <param name="parameters">The parameters.</param> public void MyMethod(List<int> parameters) { // Process the list } }

Fortunately, the .NET team thought this one through, and you can use curly brackets in the XML comments instead:

using System.Collections.Generic; public class MyProgram { /// <summary> /// Processes the contents of a <see cref="List{T}" />. /// </summary> /// <param name="parameters">The parameters.</param> public void MyMethod(List<int> parameters) { // Process the list } }

and they will be rendered as angled brackets when the documentation is created.

btw, if you're curious, the code samples were formatted using CarlosAg's Insert Colorized Code plug-in for the most excellent Windows Live Writer.

Microsoft beats Apple

Having made the switch after more than 20 years of PCs to Apple, with the purchase of a MacBook Pro at Heathrow Airport's tax-free shopping, I've slowly been shutting down activities on my recalcitrant LG laptop. I've already got the Mac versions of Microsoft Office, Adobe Lightroom and Photoshop CS3; however, one thing that OS X is really missing is a decent blog client. By far, the best blog client going is Windows Live Writer. It's probably one of the best pieces of software to come out of Redmond in the past decade.

Until OS X can come up with something equivalent, I'm going to be using either Fusion or Parallels to let me run it in a virtual machine, along with the handful of other Windows-only applications that I use for which there are definitely no equivalents. Either that, or blog from work, where I've co-opted my Windows Server 2003 desktop to run it. During my lunch break, of course.


Metablogging is essentially blogging about blogging, and probably a good sign that the author has really run out of things to say. Given that I haven't posted anything since heading off to England for a few weeks in the sun (ha!), it looks like I've reached that point already, so I hope that excuses me.

After a few abortive weeks of trying to catch up with the old Fairfax Digital team (aka Damana, Mei and Sanson), we finally managed to all be in the same place at roughly the same time (despite the best efforts of my local garage to make me miss an entire day by messing up the installation of a wheel bearing in my car, but that's besides the point).

While comparing the merits of our MacBook Pros and why using Safari instead of Camino is bad for your health, I mentioned some of the parental controls, which as the parent of an inquisitive nine year old girl, come as an absolute Godsend, and both Sanson and Damana were very impressed by it all (not that either are in a position to need parental controls on their MBPs yet), so Damana encouraged me to blog about it.

This is not that post.

Instead, it's more of a realisation that a lot of the time, I don't blog stuff because I don't think people will be interested, that it's so obvious to me that it's equally obvious to everyone else. There can be a really bad signal-to-noise ratio on the web, and I'd rather not be contributing to the noise side of the equation. However, the point was made over lunch that what is obvious to me may not be at all obvious to just one person out there, who is even now Googling for some help on the topic that you're not writing about.

So, hopefully in the next few months, I'll be picking up the slack a bit, and writing that post about OS X's parental controls. And I still have one holiday in Europe to blog about, now that I've just about finished with the photos.