http://www.developer.com/

Back to article

Managed Extensions: Sorting Your Collection Classes


August 27, 2004

Welcome to this week's installment of .NET Tips & Techniques! Each week, award-winning Architect and Lead Programmer Tom Archer demonstrates how to perform a practical .NET programming task using either C# or Managed C++ Extensions.

In the previous article, I illustrated how to make your classes enumerable via the .NET IEnumerable and IEnumerator interfaces. In this installment of the .NET Tips and Techniques series, I'll take that lesson a step further and illustrate some step-by-step instructions on making your enumerable collections sortable as well.

Implementing the IComparable and IComparer Interfaces

If you do not already have an enumerable collection to test these steps with, you can use the Article.h file found in the previous article. This is the file also used as the beginning point in this week's sample.
  1. Derive the main class (not the collection class) from the IComparable interface:
    __gc class Article : public IComparable
    {
    };
    
  2. Implement the IComparable::CompareTo method, which allows client code to pass an object to be compared against the current instance. Note that the Article sample class has three members that can be used for sorting: Title, Author and Category and here I'm defaulting the sort to the Title member by calling the Title object's built-in CompareTo method. I can do this because the Title member is a String object and the String class implements the IComparable interface:
    __gc class Article : public IComparable
    {
    ...
    public:
      int CompareTo(Object* obj)
      {
        Article* tmp = dynamic_cast<Article*>(obj);
        return(title->CompareTo(tmp->title));
      }
    
  3. Next, you need to define a class for each sort you wish to make available for your class's clients. In this case, I've added classes that allow for sorting by Article::Author and Article::Category. As you can see, each class (ArticleAuthorComparer and ArticleCategoryComparer) derive from the IComparer interface and implements it's sole Compare method, which takes two objects to compare. Within those comparisons, I can perform whatever type of application-specific logic I need to carry about the sorting. For example, for purposes of this demo, I sorted the Author field in ascending order and the Category field in descending order.

    Even if your sorting needs are much more complex, you have complete control over specifying how the items should be sorted by returning a value of less than 0 if the first item is less than the second item, 0 if the items are equal and a value greater than 0 if the first item is greater than the second:

    __gc class Article : public IComparable
    {
    ...
      __gc class ArticleAuthorComparer : public IComparer
      {
      public:
        int Compare(Object* o1, Object* o2)
        {
          Article* p1 = dynamic_cast<Article*>(o1);
          String* p1Author = p1->Author;
    
          Article* p2 = dynamic_cast<Article*>(o2);
          String* p2Author = p2->Author;
    
          // ascending order
          return 1 * p1Author->CompareTo(p2Author);
        }
      };
    
      __gc class ArticleCategoryComparer : public IComparer
      {
      public:
        int Compare(Object* o1, Object* o2)
        {
          Article* p1 = dynamic_cast<Article*>(o1);
          String* p1Category = p1->Category;
    
          Article* p2 = dynamic_cast<Article*>(o2);
          String* p2Category = p2->Category;
    
          // descending order
          return -1 * p1Category->CompareTo(p2Category);
        }
      };
    
  4. While not mandatory, I like to define an enum that client code can use to specify the current sort order. I find this much more self-documenting than using a simple data type such as an int as it forces the client code to specify a value that can be checked at runtime for accuracy. Here are the enum values used in the demo application:
    __value enum ArticleSortOption
    {
      ByTitle,
      ByAuthor,
      ByCategory
    };
    
  5. The last step to making your collection class sortable is to define a sort method that the client can call. As you can see, this public method takes the enum value defined in the previous step as its sole argument and sorts the collection based on that value:
    __gc class ArticleCollection : public IEnumerable
    {
    ...
    protected:
      ArticleSortOption sortOption;
    public:
      void Sort(enum ArticleSortOption sortOption)
      {
        this->sortOption = sortOption;
        switch(sortOption)
        {
          case ArticleSortOption::ByTitle:
          articles->Sort();
          break;
    
          case ArticleSortOption::ByAuthor:
          articles->Sort(new Article::ArticleAuthorComparer);
          break;
    
          case ArticleSortOption::ByCategory:
          articles->Sort(new Article::ArticleCategoryComparer);
          break;
        }
      }
    

The Client Side

At this point, the client side of things is very simple. In order to enumerate the list, simple follow the instructions explained in the previous article and to sort the collection, you need only call the collection object's Sort method passing it the appropriate sort enum value. For example, this article's demo has an mixed mode application (MFC & .NET) that displays a collection of articles in a listview control. When the user clicks on any of the listview control's header columns, the list is then sorted. Here's that particular function:
void CCollectionSortingDlg::OnHdnItemclickList1(NMHDR *pNMHDR, LRESULT *pResult)
{
  UpdateData();
	
  LPNMHEADER phdr = reinterpret_cast<LPNMHEADER>(pNMHDR);
  switch( phdr->iItem )
  {
    case 0: 
      m_articles->Sort(ArticleSortOption::ByTitle);
      m_strSortedBy = _T("Title");
    break;

    case 1:
      m_articles->Sort(ArticleSortOption::ByAuthor);
      m_strSortedBy = _T("Author");
    break;

    case 2:
      m_articles->Sort(ArticleSortOption::ByCategory);
      m_strSortedBy = _T("Category (DESC)");
    break;
  }

  UpdateData(FALSE);
  FillArticleList();

  *pResult = 0;
}

Dynamically Handling Additions to the Collection

The last thing I'll cover is how to dynamically deal with new data being added to the collection. The client code (the user interface) filled the listview control by enumerating the collection of Article objects. However, it would be extremely inefficient to do that each time a new Article object is added to the collection. A much better mechanism is to add a method to the ArticleCollection class that will return the actual (sorted) index of the newly added Article object. As the Article objects are stored in an ArrayList object and the ArticleList class implements a method called IndexOf, our work is mostly done for us.

In the following code snippet, you can see the following additions:

  • ArticleCollection::Add—allows the client to add a new Article object
  • ArticleCollection::IndexOf—Allows the client to determine the index of the newly added Article object. Note that the collection is re-sorted such that the index is based on the current sort order:
__gc class ArticleCollection : public IEnumerable
{
...
public:  
  int Add(Object* value)
  {
    return articles->Add(value);
  }

  int IndexOf(Article* article)
  {
    Sort(this->sortOption);
    return articles->IndexOf(article);
  }
With these two new ArticleCollection methods, the client can use code similar to the following in order to insert the new Article data into the listview control at the proper place:
void CCollectionSortingDlg::OnBnClickedAdd()
{
...
  // Add to collection
  m_articles->Add(article);

  // Ask collection where new item was inserted
  int i = m_articles->IndexOf(article);

  // Based on item's insertion point, insert
  // item into list control at same index
  int idx = m_lstArticles.InsertItem(i, m_strTitle);
  m_lstArticles.SetItemText(idx, 1, m_strAuthor);
  m_lstArticles.SetItemText(idx, 2, m_strCategory);
  ...

Looking Ahead

At this point, you've seen how to make your classes enumerable and how to sort those enumerable collections. However, one issue that you might have noticed in the last section of this article is that we're assuming that only one client will be using a given ArticleCollection object. In other words, while this code works perfectly in a situation where a single ArticleCollection instance is being used by a single client, we would run into problems if multiple clients were using the same ArticleCollection as each time data was added to the collection (or the collection was sorted), it would muck things up for the other clients. This gets into the issue of versioning—the ability to uniquely identify instances of the collection object such that one client doesn't invalidate the integrity of the collection object for other clients that are concurrently using it. Therefore, the subject of versioning collections will be covered in the next article.

Download the Code

To download the accompanying source code for this article, click here.

About the Author

The founder of the Archer Consulting Group (ACG), Tom Archer has been the project lead on three award-winning applications and is a best-selling author of 10 programming books as well as countless magazine and online articles.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date