Friday, November 13, 2009

More than one way to sort a List<>

One operation you occasionally need to perform is sorting a generic list of objects. Often developers code handle with an inline delegate. If I wanted to sort a collection of boardgames by rating, my code might look like this:

  games.Sort(
      delegate(BoardGame a, BoardGame b)
      {
          if (a.Rating < b.Rating)
              return 1;
          if (a.Rating > b.Rating)
              return -1;
          return 0;
      });

Although this works, it tends to look a bit cluttered. It also increases a method's complexity and doesn't adhere to the idea of separation of concerns. If we only need to sort Boardgames in this one location, a better solution is to move the comparison code into a separate method in the same class. The new method looks like this:

  public int CompareBoardgamesByRank(BoardGame a, BoardGame b)
  {
      if (a.Rating < b.Rating)
          return 1;
      if (a.Rating > b.Rating)
          return -1;
      return 0;
  }

Our call to Sort looks like this:

  games.Sort(CompareBoardgamesByRank);

This makes the code much cleaner for a single sort. If we want to sort Boardgames by rating from multiple locations, we need a better place to store the comparison method. If we implement IComparable on our Boardgame class, we can create a CompareTo method on the class like so:

  public int CompareTo(object obj)
  {
      BoardGame b = (BoardGame)obj;
 
      if (Rating < b.Rating)
          return 1;
      if (Rating > b.Rating)
          return -1;
      return 0;
  }

Since the class now has a default comparison, we no longer need to specify a delegate when calling Sort - the CompareTo method will automatically be executed with an empty call:

  games.Sort();

What if we need additional comparison methods for our Boardgame? An easy way to handle this is to create static methods on the class, which will be available anywhere that Boardgame can be accessed. Here are a couple methods I've added to my Boardgame class:

  public static int CompareByName(BoardGame a, BoardGame b)
  {
      return string.Compare(a.Name, b.Name);
  }
 
  public static int CompareByRankAssending(BoardGame a, BoardGame b)
  {
      if (a.Rating > b.Rating)
          return 1;
      if (a.Rating < b.Rating)
          return -1;
      return 0;
  }

Which I can now pass into the Sort method:

  games.Sort(BoardGame.CompareByRankAssending);