Microsoft & .NETVisual C#Managed Extensions: Sorting Your Collection Classes

Managed Extensions: Sorting Your Collection Classes

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

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.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories