Click here to Skip to main content
15,881,424 members
Articles / Programming Languages / C# 5.0

C#.NET: Overriding the basic functionality of IComparable<T> and IComparer<T> interfaces to sort custom collections of data using Enumerable.OrderBy<TSource,TKey> aggregate method.

Rate me:
Please Sign up or sign in to vote.
4.99/5 (49 votes)
30 Jun 2015CPOL8 min read 24.5K   272   44   9
The following article describes how to implement sorting of custom non-generic collections by overriding the functionality of the generic IComparable<T> and IComparer<T> interfaces used by the LINQ’s Enumerable.OrderBy<TSource, TKey> clause aggregate method.

Introduction

Whenever codding in C#.NET, we're implementing custom collections to store multiple related data object items of the same custom data type, as well as either providing the functionality that allows to manipulate those data objects items specific to their certain data type. In C# programming we also are using the generic features of the Microsoft .NET Framework and other language extensions, such LINQ, that, in turn, are allowing to manipulate the data items stored within the generic collections of data by using the LINQ query and aggregation methods. Unfortunately, these features are typically only used along with the generic collections of data such as List<T>, ArrayList, Stack<T>, etc. The following article describes the aproach that makes it possible to provide the extended functionality to the LINQ queries and aggregation methods when used to manipulate the items of a custom user-defined data type, stored within the custom non-generic collection of data implemented.

Background

In this article, we will discuss the common scenario which allows to override the generic functionality of the LINQ aggregation method <font face="Courier New">Enumerable.OrderBy<TSource, TKey></font> to perform the ascending sorting of the data object items of a certain user-defined data type, stored in the custom non-generic collection of data designed. As the example that illustrates the concept, described below, we will use the Enumerable.OrderBy aggregation method to sort the data object items containing the floating-point decimal datafield. This floating-point datafield will represent the key value according to which we'll be performing the sorting and other custom aggregate operations on the items stored in the custom collection of data. In order, to use the LINQ aggregation methods with data object items of the custom data type we actually need to override the generic delegate comparer methods, that perform the comparisson of the two data object items. For that purpose we need to derive a class from either IComparable<T> or IComparer<T> and re-implement the methods by providing the custom functionality that allows to sort the data object item's values of a specific data type. Refer to the using the code section of this article for the detailed step-by-step explaination of the following approach. 

Using the code

Suppose, we have a class <font face="Courier New">Item</font> designed, that represents an item stored within a certain custom non-genetic collection of data. Normally, this class contains either the data fields that are assigned to a single value of a certain data type, or methods to manipulate each particular item of the collection of data implemented. In this case, we're declaring a private variable <font face="Courier New">m_value</font> of floating-point data type. Also we're providing the constructor that is used to assign the value to the <font face="Courier New">m_value</font> variable at the point when a <font face="Courier New">Item</font> class object is being initialized. To be able to use each <font face="Courier New">Item</font> object along with the LINQ aggregate functions, we also should derive this class from the generic <font face="Courier New">IComparable<T> </font>interface and specify the type parameter T a value of the <font face="Courier New">Item</font> class. The IComparable<T> interface contains only one method <font face="Courier New">int CompareTo(T object)</font>, that is invoked by the LINQ aggregate methods like <font face="Courier New">OrderBy<T, TKey>, Average<T>, Max<T>, Min<T></font>, to compare each item within a certain collection of data<font face="Courier New">.</font> Normally, this method inherited will take another <font face="Courier New">Item</font> object value as the parameter and is comparing it with the current data object item. As the result of comparing the two data object items this method returns the value that is greater than zero if the current item is less than another item compared, less than zero if it's greater, or zero if the two object items are equal respectively. Another method implemented in the following class is <font face="Courier New">float GetItem() </font>used to retrieve the value of the private float data field used by the value retrieving and sorting methods described below.

C#
class Item : IComparable<Item>
{
    private float m_value; // private float data field
    public  Item(float value) { m_value = value; }
    public  float GetItem() { return m_value; }
    public  int CompareTo(Item other)
    {
        // Comparing each item floating value using the following conditions
        return (this.GetItem() != other.GetItem()) ?
            ((this.GetItem() < other.GetItem()) ? 1 : -1) : 0;
    }
}

Another non-generic class we have to implement is the <font face="Courier New">ItemsComparer</font>. This class, when is derived from <font face="Courier New">IComparer<T></font> interface, encapsulates the basic mechanism of the each collection's data object item comparison performed by an aggregation method invoked. So, when designing the <font face="Courier New">ItemsComparer</font> class we're actually deriving it from the <font face="Courier New">IComparer<T> </font>interface and, similarly to the previous class <font face="Courier New">Item</font> implementation, assign the type parameter T the value of the <font face="Courier New">Item</font> class, which means that the all methods derived from IComparer<T> will have its parameters of the <font face="Courier New">Item</font> class type. Similarly to the class <font face="Courier New">Item</font> a class derived from <font face="Courier New">IComparer<T>, ItemsComparer</font> class can only inherit one method <font face="Courier New">int Compare(T x, T y)</font> overloaded. This method provides the basic functionality for comparing a pair of the collection's items of a certain type. In this case, the difference between this method and, previously recalled, <font face="Courier New">IComparable<T>.CompareTo</font> is that the following method is invoked to compare the entire object items, stored in the collection of data designed, while the IComparable<T>.CompareTo(T other) is used to compare particular data fields within the data object item's class <font face="Courier New">Item. </font>For example, if IComparable<T>.CompareTo(T other) is used to compare two float values of <font face="Courier New">m_value</font> member variable from two different instances of an <font face="Courier New">Item</font> object, then <font face="Courier New">IComparer<T>.Compare(T x, Ty)</font> will be used to perform the same comparisson on the pair of instances of the <font face="Courier New">Item</font> data object type within the collection of data designed.  The returning value of the IComparer<T>.Compare(T x, T y) method can be evaluated as follows: if the value of x is less than y it returns -1, or if the value of x is greater than y the following method returns 1, it returns 0 if both x and y are equal. In this particular case, we actually are not evaluating the returning value of this method, instead we're invoking the IComparable<T>.CompareTo method implemented in the class Item and assign it has returned to value to the returning value of the IComparable<T>.CompareTo method, so this value is returned outside of these methods and is used by the Linq aggregation methods such as Enumerable.OrderBy<TSource, TKey> at the time it has been invoked. 

C#
    class ItemsComparer : IComparer<Item>
    {
        public int Compare(Item x, Item y)
        {
            // Invoking a CompareTo method to compare each 
            // item x of the collection with the current item y specified
            return x.CompareTo(y);
        }
    }

The following portion of C# code implements a custom non-generic collection of data that allows store the array of values of the Item object data type as well as providing some functionality of either appending and retrieving items from the collection, or implementing the <font face="Courier New">IEnumerable.GetEnumerator()</font> methods instances that allow to obtain the collection's enumerator object, that will further be invoked by the C# foreach statement to iterate through the items stored in the collection of data. Normally, to implement a custom non-generic collection of data, in this C# code sample, we won't use the existing generic collections of data such as <font face="Courier New">List<T>, ArrayList</font>, <font face="Courier New">Stack<T></font>, etc. Instead, we're implementing the collection based on the using C# simple arrays to store the multiple data objects of the <font face="Courier New">Item</font> data type within. The reason why we don't derive the <font face="Courier New">MyItemsCollection</font> class from the one of the generic collections is that they already implement the sorting and other aggregate methods to manipulate the data items stored in the collection of data. We actually are implementing the custom non-generic collection from "a scratch" and don't need any of those generic methods to be overloaded. To sort the items stored in the collection, we're implementing the <font face="Courier New">Sort() </font>method that invokes the LINQ's <font face="Courier New">Enumerable.OrderBy </font>clause. Let's now recall, that not only the generic collections such as <font face="Courier New">List<T></font>, <font face="Courier New">ArrayList</font>,..., but also simple arrays in C# are treated as the objects derived from the generic <font face="Courier New">IEnumerable<T></font> interface, from which we inherit the all methods provided by <font face="Courier New">IEnumerable</font> interface as well as from the other generic interfaces, the IEnumerable is derived from. Normally we're passing the first and second type parameters the values of either the type of data items being sorted or the type of key the value by which the sort will be performed, respectively<font face="Courier New">. </font>In this case, these two type parameters are assigned to the value of the <font face="Courier New">Item</font> data object. The first argument of the Enumerable.OrderBy method is the lambda expression which is used to specified what particular sequence of values will be ordered. The next argument of the following method is an object of the <font face="Courier New">IComparer<T></font> derived class constructed. This object will further be used to invoke the IComparer.Compare method which is intended to performs the actual comparisson between the two object items of Item type.

C#
class MyItemsCollection : IEnumerable<Item>
{
    // The array of items of Item data type
    private static Item[] m_Items = null;
    // The array items index variable m_ItemIndex
    private static int m_ItemIndex = 0;
    public MyItemsCollection()
    {
        // Initializing the array of items
        if (m_Items == null)
            m_Items = new Item[0];
    }

    public void Add(Item item)
    {
        // Growing the size of the array
        Array.Resize<Item>(ref m_Items, m_ItemIndex + 1);
        // Assigning a value to the current item of m_Items array
        // accessed by its m_ItemIndex index
        m_Items[m_ItemIndex++] = item;
    }

    public void Sort()
    {
        // Invoking orderby LINQ aggregation method to sort
        // the items stored in the array m_Items
        m_Items = m_Items.OrderBy<Item, Item>(item => item,
                new ItemsComparer()).ToArray();
    }

    public IEnumerator GetEnumerator()
    {
        // Retriving the value of the generic enumerator
        // for the array m_Items
        return m_Items.GetEnumerator();
    }

    IEnumerator<Item> IEnumerable<Item>.GetEnumerator()
    {
        // Invoking the GetEnumerator() methods of this object
        // and assign its returning value to the returning value
        // of this function explicitly casting it to the type of IEnumerator<Item>
        return (IEnumerator<Item>)this.GetEnumerator();
    }
}

Finally, the C# code implemented in the <font face="Courier New">Program</font> class is intended to initialize the <font face="Courier New">MyItemsCollection</font> class object. After the initialization we're appending five data object items, each one is the dynamically constructed object of the described above <font face="Courier New">Item</font> class. In this case, the value of the float type is assigned of the constructor parameter of each <font face="Courier New">Item </font>class object is being instantiated. After the collection initialization, we use the C# foreach statement to iterate through the items in the collection of data, fetching each float value by invoking <font face="Courier New">GetItem()</font> method for each instance of the data item object and performing the console output for each float item's value retrieved.

C#
class Program
{
    static void Main(string[] args)
    {
        // Initializing the custom collection's object
        MyItemsCollection items = new MyItemsCollection();
        // Adding items to the collection of data
        items.Add(new Item(3.5F));
        items.Add(new Item(0.58F));
        items.Add(new Item(1.16F));
        items.Add(new Item(0.32F));
        items.Add(new Item(0.57F));

        // Sorting the collection of data
        items.Sort();

        // Iterating through the collection of data constructed
        // Retrieving items using GetItem() method and providing
        // the console output for each item fetched from the current collection
        foreach (Item item in items)
            Console.WriteLine("{0:F}", item.GetItem());

        Console.ReadKey();
    }
}

 

Points of Interest

The programming approach described in the following article can be used for implementing the custom non-generic collections of data in C#.NET as well as providing the functionality for manipulating the data object items of the user-defined type, stored in custom collections of data, by using the LINQ aggregation methods. This article is also intended for the beginner C# developers and enterpreneurs as the brief tutorial to the basics of the custom non-generic collections of data implementation and other object-oriented programming techniques used in C# programming language.  

History

  • June 28, 2015 - The first version of the article was published

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) EpsilonDev
Ukraine Ukraine
I’m software developer, system analyst and network engineer, with over 20 years experience, graduated from L’viv State Polytechnic University and earned my computer science and information technology master’s degree in January 2004. My professional career began as a financial and accounting software developer in EpsilonDev company, located at L’viv, Ukraine. My favorite programming languages - C/C++, C#.NET, Java, ASP.NET, Node.js/JavaScript, PHP, Perl, Python, SQL, HTML5, etc. While developing applications, I basically use various of IDE’s and development tools, including Microsoft Visual Studio/Code, Eclipse IDE for Linux, IntelliJ/IDEA for writing code in Java. My professional interests basically include data processing and analysis algorithms, artificial intelligence and data mining, system analysis, modern high-performance computing (HPC), development of client-server web-applications using various of libraries, frameworks and tools. I’m also interested in cloud-computing, system security audit, IoT, networking architecture design, hardware engineering, technical writing, etc. Besides of software development, I also admire to write and compose technical articles, walkthroughs and reviews about the new IT- technological trends and industrial content. I published my first article at CodeProject in June 2015.

Comments and Discussions

 
QuestionI'm a little confused about the main point Pin
OneWinsto11-Jul-15 11:56
OneWinsto11-Jul-15 11:56 
AnswerRe: I'm a little confused about the main point Pin
Arthur V. Ratz11-Jul-15 20:17
professionalArthur V. Ratz11-Jul-15 20:17 
GeneralRe: I'm a little confused about the main point Pin
OneWinsto14-Jul-15 8:49
OneWinsto14-Jul-15 8:49 
Thanks for your reply (and sorry it took me a while to get back to you.)

I do see your points, but I still think some parts of the article are a little confusing. I think some of the confusion comes from phrases such as 'generic collection such as List and ArrayList'. It looks like by 'generic' you mean part of the base class library. Whereas when see the word 'generic' I tend to think of .NET generics; in which case List<T> is generic, but ArrayList is not.

As for point 1: I understand why you implemented IComprarer<Item> to use in the OrderBy extension method, but that is all you needed to pass to this method. You don't need to have your class implement IComparable in order to compare in the OrderBy method.

Point 2: I can see the sense in having a custom collection; to prevent things like clients modifying that collection. This is, in fact, considered good practice when exposing collections to clients.

Point 3: I think this gets back to my initial question. You don't need to implement both interfaces to achieve what your article mainly seems to be about; that is using OrderBy with custom comparison using only public access to instances or your Item type.

If you called the override of OrderBy not taking a Comparer argument, then the would use the default comparer for your type and your implementation of Compare would be called. If that was the intention maybe having two different sort methods in your example might make it clearer; one using a Comparer and one using the default comparer.

The other scenario (as I speculated on in my question) would be accessing private data within the class for comparison.

As the article stands, with your example code, the implementation of IComparable isn't necessary.
QuestionI prefer the use of delegates Pin
Sacha Barber1-Jul-15 2:43
Sacha Barber1-Jul-15 2:43 
AnswerRe: I prefer the use of delegates Pin
Arthur V. Ratz1-Jul-15 3:16
professionalArthur V. Ratz1-Jul-15 3:16 
AnswerRe: I prefer the use of delegates Pin
Paulo Zemek1-Jul-15 14:28
mvaPaulo Zemek1-Jul-15 14:28 
GeneralRe: I prefer the use of delegates Pin
Arthur V. Ratz1-Jul-15 16:55
professionalArthur V. Ratz1-Jul-15 16:55 
GeneralRe: I prefer the use of delegates Pin
Paulo Zemek1-Jul-15 18:42
mvaPaulo Zemek1-Jul-15 18:42 
GeneralRe: I prefer the use of delegates Pin
Arthur V. Ratz1-Jul-15 19:20
professionalArthur V. Ratz1-Jul-15 19:20 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.