Managed Extensions: Sorting Your Collection Classes
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.- Derive the main class (not the collection class) from the IComparable interface:
__gc class Article : public IComparable { }; - 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)); } -
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); } }; -
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 }; -
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; } }
