Back in the turn of the last century, the golden days of .NET 1.1, if you wanted to implement a type-safe custom collection your best bet was to derive from CollectionBase in the System.Collections namespace. CollectionBase was more than just an abstract base class. It was also a wrapper around an internal ArrayList. If you wanted to sort strongly-typed objects in the collection you would implement an IComparer class:

inner class AscendingWidgetSorter : IComparer {
    public int Compare(Object x, Object y) {
        Widget w1 = (Widget)x;
    IComparable ic1 = (IComparable)w1.Size;
    Widget w2 = (Widget)y;
    IComparable ic2 = (IComparable)w2.Size;
    return ic1.CompareTo(ic2);
    }
}

The IComparer implementation could then be passed into a Sort method in your custom collection:

public void Sort() {
    IComparer widgetSorter = new AscendingWidgetSorter();
    InnerList.Sort(widgetSorter);
} 

Fast forward to .NET 2.0/3.5 and we now have support for generics in IComparer which is in the System.Collections.Generic namespace. With the use of templates you no longer have to cast the type inside the Compare method, you can declare the type with the IComparer implementation. Let's say we want to compare square widgets and provide support for sorting them. They come in four sizes labelled A, B, C, and D. Further, if two widgets are of the same size then the larger of the two is the one with the most teeth. A square widget can have anywhere from 4 to 8 teeth. Here's the IComparer implementation:

/// <summary>
/// Widget sizes range from the smallest A through the largest D and teeth 
/// on any particular widget can be as few as 4 and as many as 8. The sort 
/// algorithm must sort from smallest to largest first by size then by the
/// tooth count.
/// </summary>
internal class WidgetSorter : IComparer<SquareWidget> {
    /// <summary>
    /// Returns the smaller of the two widgets where:
    ///     less than 0    - sw1 is smaller than sw2
    ///     0              - sw1 is equal in size to sw2
    ///     greater than 0 - sw1 is greater than sw2 or sw2 is null
    /// </summary>
    /// <param name="sw1"></param>
    /// <param name="sw2"></param>
    /// <returns></returns>
    public int Compare(SquareWidget sw1, SquareWidget sw2) {
        if (sw1.Size.Equals(sw2.Size)) {
            return sw1.Teeth.CompareTo(sw2.Teeth);
        }
        else {
            return sw1.Size.CompareTo(sw2.Size);
        }
    }
}

And of course we need to describe a SquareWidget in our system:

public class SquareWidget {
    private int _id;
    private string _size;
    private int _teeth;
 
    public SquareWidget(int id, string size, int teeth) {
        this._id = id;
        this._size = size;
        this._teeth = teeth;
    }
       
    public int ID {
        get { return this._id; }
    }
 
    public string Size {
        get { return this._size; }
    }
 
    public int Teeth {
        get { return this._teeth; }
    }
}

From there the behavior is the same as before. You create an instance of the WidgetSorter and pass that into the List.Sort method. Here's a sample console app that exercises the code above with a List<T> thrown in for good measure:

class Program {
    static void Main(string[] args) {
 
        List<SquareWidget> widgetList = new List<SquareWidget>();
           
        // load widgets
        widgetList.Add(new SquareWidget(1, "C", 5));
        widgetList.Add(new SquareWidget(2, "A", 4));
        widgetList.Add(new SquareWidget(3, "D", 8));
        widgetList.Add(new SquareWidget(4, "A", 8));
        widgetList.Add(new SquareWidget(5, "D", 7));
        widgetList.Add(new SquareWidget(6, "C", 4));
        widgetList.Add(new SquareWidget(7, "A", 5));
       
        // instantiate our custom widget sorter and do the sort
        IComparer<SquareWidget> widgetSorter = new WidgetSorter();
        widgetList.Sort(widgetSorter);
 
        foreach (SquareWidget sw in widgetList) {
            Console.WriteLine(string.Format(
                "ID: {0} Size: {1} Teeth: {2}", sw.ID, sw.Size, sw.Teeth));
        }
        Console.ReadLine();
    }
}

If you run the app you'll get the list sorted according to our specifications:

ID: 2 Size: A Teeth: 4
ID: 7 Size: A Teeth: 5
ID: 4 Size: A Teeth: 8
ID: 6 Size: C Teeth: 4
ID: 1 Size: C Teeth: 5
ID: 5 Size: D Teeth: 7
ID: 3 Size: D Teeth: 8

Enjoy!