[.net] [C#] Sorting, IComparer and inheritance

Started by
6 comments, last by Endar 17 years, 1 month ago
Hey there, I'm attempting to sort some List<> objects. The templated object types are different for each list, but they all have the same base class and I've written a set of IComparer classes that sort based on different attributes of the base class. Now the compiler is throwing a whole bunch of errors at me such as:

Argument '1': cannot convert from 'System.Collections.Generic.IComparer<ToDoList.ToDoBase>'
 to 'System.Collections.Generic.IComparer<ToDoList.ToDoGroup>'
Where 'ToDoBase' is the base class and 'ToDoGroup' is the derived class. Is there a way around this, or do I have to write up a set for every derived object type?
[size="2"][size=2]Mort, Duke of Sto Helit: NON TIMETIS MESSOR -- Don't Fear The Reaper
Advertisement
Okay, well I just re-wrote all the compare classes to have 2 for each sorting criteria (because there are 2 derived classes).

So, I have something like 'NameAlphabeticalCompareGroup' that inherits from 'IComparer<ToDoGroup>', but I'm still getting error messages. Ones that don't make much sense to me:
/** * Sort the node subtree by Name */ private void nameToolStripMenuItem1_Click(object sender, EventArgs e){    if (CheckIfCurrentTreeNodeIsValid())        Sort(ToDoList_heirarchy.SelectedNode,             new NameAlphabeticalCompareGroup(), new NameAlphabeticalCompareItem());}


The errors I'm getting for that one function call:
The best overloaded method match for 'ToDoList.ToDoList_form.Sort(System.Windows.Forms.TreeNode, ref System.Collections.Generic.IComparer<ToDoList.ToDoGroup>, ref System.Collections.Generic.IComparer<ToDoList.ToDoItem>)' has some invalid argumentsArgument '2': cannot convert from 'ToDoList.NameAlphabeticalCompareGroup' to 'ref System.Collections.Generic.IComparer<ToDoList.ToDoGroup>'Argument '3': cannot convert from 'ToDoList.NameAlphabeticalCompareItem' to 'ref System.Collections.Generic.IComparer<ToDoList.ToDoItem>'


private void Sort(TreeNode node, ref IComparer<ToDoGroup> group_comp, ref IComparer<ToDoItem> item_comp){    ToDoGroup temp = (ToDoGroup)node.Tag;    ProgramInstance.Sort(ref temp, ref group_comp, ref item_comp, true);    // create the subtree for the treeview node again because it has changed.    node = ConstructTreeViewNode(ref temp);    // a change has been made    ChangesSaved = false;}


What is going on?
[size="2"][size=2]Mort, Duke of Sto Helit: NON TIMETIS MESSOR -- Don't Fear The Reaper
You want to sort these todo things based on some property? Like on todo.Priority?

The simplest way to do that is to just use a little compare function, rather than a whole class:

{    List<SomeClass> list = new List<SomeClass>();    list.Sort(sortSomeClassFunction);}private static int sortSomeClassFunction(object o1, object o2){    SomeClass sc1 = (SomeClass)o1;    SomeClass sc2 = (SomeClass)o2;    // compare and return }


That particular overload of list.Sort is formally declared to take a generic delegate (i.e. Comparison<T> comparer), but it will accept a function typed to take objects.

For just sorting a list, you don't need an actual object because the List won't hold onto it for use during other operations the way a Dictionary or SortedList will.
Quote:Original post by dalep
That particular overload of list.Sort is formally declared to take a generic delegate (i.e. Comparison<T> comparer), but it will accept a function typed to take objects.

For just sorting a list, you don't need an actual object because the List won't hold onto it for use during other operations the way a Dictionary or SortedList will.


Okay, but since I'm going to be passing the function that I'm using through a couple of functions before it is used, I declared a delegate.

It gets passed like this: EventHandlerFunction -> frm.Sort -> Program.Sort

"Program" is actually a class name. It really shouldn't be but, for the moment, it is. [smile]

Okay, here's a quick sample of what I have now:
    public class ComparisonFunctions    {        //! Define a delegate (like a definition of the function signature of the functions) for comparison functions        public delegate int ComparisonFunction(object x, object y);        /**         * Compare the two objects.         * \param x The first object to compare         * \param y The second object to compare         * \return Less than zero - x is less than y, Zero - x equals y, Greater than zero - x is greater than y         */        public static int NameAlphabeticalCompare(object x, object y)        {            ToDoBase a = (ToDoBase)x;            ToDoBase b = (ToDoBase)y;            // return which is greater if one of the objects is null            if (a == null)            {                if (b == null)                    return 0;                else                    // because b is "greater" than a, because b exists and a does not                    return -1;            }            // if a != null and b == null            else if (b == null)            {                // because a is "greater" than b, because a exists and b does not                return 1;            }            // now compare the actual objects            return a.Name.CompareTo(b.Name);        }        // other sorting functions}


And I'm using 'ComparisonFunction' as the parameter type to pass it through functions. And it works all the way up until the List.Sort function.
Argument '1': cannot convert from 'ToDoList.ComparisonFunctions.ComparisonFunction' to 'System.Collections.Generic.IComparer<ToDoList.ToDoGroup>'


Should I be using a different variation of the List.Sort function?
[size="2"][size=2]Mort, Duke of Sto Helit: NON TIMETIS MESSOR -- Don't Fear The Reaper
as dalep said,

there are two interfaces for IComparer,

IComparer
and
IComparer<T>

Dont use the templated interface, just use the old object based one.

so do:

private void Sort(TreeNode node, ref IComparer group_comp, ref IComparer item_comp)


not

private void Sort(TreeNode node, ref IComparer<ToDoGroup> group_comp, ref IComparer<ToDoItem> item_comp)


Either that or implement the non generic IComparable interface on the base class, and call Sort() without any comparer.
I have a GUI system that I wrote which has a sorter for children of any of the objects... the problem I ran in to was that the "label" object (which inherits from the base class object, where the sorter is) has its own "text" property that is only on the label object, but not the base. So if you wanted to sort a bunch of labels (like in a listbox), you'd either have to override the sorter, or you could do what I did...

I put in a "sort method" enum in, with values such as "ByName" (string name of control), "ByTagID", or "ByText". Here's the sort code (it's VB.NET, but it should translate well).

    Private Class ListComparer        Implements IComparer(Of guiControlBase)        Function Compare(ByVal x As guiControlBase, ByVal y As guiControlBase) As Integer Implements IComparer(Of EEGUI.guiControlBase).Compare            Dim RetVal As Integer = 0            Select Case eSortingMethod                Case SortingMethods.ByName                    RetVal = String.Compare(x.Name, y.Name)                Case SortingMethods.ByTagID                    If x.TagID > y.TagID Then                        RetVal = 1                    ElseIf x.TagID < y.TagID Then                        RetVal = -1                    End If                Case SortingMethods.ByTagString                    RetVal = String.Compare(x.TagString, y.TagString)                Case SortingMethods.ByText                    If Not CType(x, guiLabel) Is Nothing AndAlso Not CType(y, guiLabel) Is Nothing Then                        RetVal = String.Compare(CType(x, guiLabel).Text, CType(y, guiLabel).Text)                    End If            End Select            If Not bSortAscending Then                RetVal *= -1            End If            Return RetVal        End Function    End Class


See how if the sorting method is "ByText", I specifically cast the X and Y objects in to "guiLabel", so I can grab the text value out of it? Otherwise, the objects are just "guiControlBase" objects.

The "not ... is nothing" checks are to ensure that those objects are indeed guilabel objects.
Jared "EagleEye" Mark
Quote:Original post by RipTorn
as dalep said,

there are two interfaces for IComparer,

IComparer
and
IComparer<T>

Dont use the templated interface, just use the old object based one.

so do:

private void Sort(TreeNode node, ref IComparer group_comp, ref IComparer item_comp)


not

private void Sort(TreeNode node, ref IComparer<ToDoGroup> group_comp, ref IComparer<ToDoItem> item_comp)


Either that or implement the non generic IComparable interface on the base class, and call Sort() without any comparer.


At the moment, to get it working, I have all my seperate classes again, each with 1 "int Compare(object,object)" function. I'm still getting compile errors.

I keep getting errors that say that "IComparer" cannot be converted to "IComparer<ToDoGroup>". This happens only on the call to List.Sort.

Obviously, the List's template parameter is ToDoGroup, so what am I doing wrong? Apparently List.Sort has 4 overloads, none of which take a normal "IComparer" object.

Edit:: Is it possible to do it like this, or am I going to have to have my data (ToDoGroup) classes inherit from IComparable?
[size="2"][size=2]Mort, Duke of Sto Helit: NON TIMETIS MESSOR -- Don't Fear The Reaper
Works.

I had the base class inherit from IComparable, and wrote the base::CompareTo function. I also stuck a static member variable as an enum, which describes which type of comparision to do. ie. By name, by date, etc.

/** * The possible search criteria */ public enum SortCriteria{    Name,               ///< Sort by the name    DateCreated,        ///< Sort by the creation date    DateCompleted,      ///< Sort by the completion date    IsComplete,         ///< Sort by the completion status    DateUpdated         ///< Sort by the date last updated    }/** * A class to serve as an abstract base class for the ToDo group and ToDo item objects. */public class ToDoBase   :   IComparable{    public string Name;                  ///< The name of the item    public DateTime DateCreated;         ///< The date this item was created    public DateTime DateCompleted;       ///< The date this item was marked complete    public bool IsComplete;              ///< If the item has been marked as complete or not    public ToDoGroup Parent;             ///< The parent group of this item    public SortCriteria SortingCriteria;    ///< The criteria to sort by    /**     * Constructor     * \param name The name of the item     * \param creation_date The date the item was created     */    public ToDoBase(string name, DateTime creation_date, ToDoGroup parent)    {        Name = name;        DateCreated = creation_date;        Parent = parent;    }    /**     * Compare this object with another     * \param obj The object to compare to     */     public int CompareTo(object obj)    {        ToDoBase b = (ToDoBase) obj;        if (b == null)            return 1;        if (SortingCriteria == SortCriteria.Name)        {            return this.Name.CompareTo(b.Name);        }        else if (SortingCriteria == SortCriteria.IsComplete)        {            return this.IsComplete.CompareTo(b.IsComplete);        }        else if (SortingCriteria == SortCriteria.DateCreated)        {            return this.DateCreated.CompareTo(b.DateCreated);        }        else if (SortingCriteria == SortCriteria.DateCompleted)        {            // check for invalid dates, make sure that both aren't invalid            if( this.DateCompleted != b.DateCompleted ){                // if one of the dates is invalid                if (this.DateCompleted == new DateTime(1, 1, 1))                    return -1;                else if (b.DateCompleted == new DateTime(1, 1, 1))                    return 1;            }            return this.DateCompleted.CompareTo(b.DateCompleted);        }        // If sorting by updated date, only ToDoItems have an updated date.        // Write checks in to make sure that only ToDoItems are attempted to sort by this criteria.        else if (SortingCriteria == SortCriteria.DateUpdated)        {        }        return 0;   // just as a default, invalid value    }// CompareTo}// class ToDoBase


Thanks for all the help, guys.
[size="2"][size=2]Mort, Duke of Sto Helit: NON TIMETIS MESSOR -- Don't Fear The Reaper

This topic is closed to new replies.

Advertisement