Here is my class with integer and string variables

class foo
        {
            int age;
            string name;
        }

How do I create an IComparer that takes in foo as a class without the need for casting from an object?

class SortAgeAscendingHelper : IComparer
        {
            int IComparer.Compare(object a, object b)
            {
                foo f1 = (foo)a;
                foo f2 = (foo)b;

                if (f1.age > f2.age)
                {
                    return 1;
                }
                if (f1.age < f2.age)
                {
                    return -1;
                }
                else
                {
                    return 0;
                }
            }
        }

        public static IComparer SortAgeAscending()
        {
            return (IComparer)new SortAgeAscendingHelper();
        }

I only wish to sort a certain index range of an array of foo's

Array.Sort(foos, 0, fooCount, SortAgeAscending());

so I can't see any other way than to use IComparer, is this really the case?

Recommended Answers

All 9 Replies

Overrides GetHashCode, and Equals.

class foo
{
    int age;
    string name;
    public override int GetHashCode()
    {
        return age;        
    }
    public override bool Equals(object obj)
    {
        return GetHashCode() == obj.GetHashCode();
    }
}

How does that work?

How can I sort by ascending/descending using this method?

That just looks completely alien to me as I can't understand it, sorry.

Are you unable or just unwilling to use the generic interface IComparer<T>? With the generic interface implementation, the method natively accepts Foo arguments. However, with the plain interface, you are left with objects and you will simply have to cast.

class FooComparer : IComparer<Foo>
{
    public int Compare(Foo x, Foo y)
    {
            // implement comparison here
    }
}

I was hoping to do the comparison using that method but I get the following error

The non-generic type 'System.Collections.IComparer' cannot be used with type arguments

What gives?

EDIT:

I forgot the following line, doh!

using System.Collections.Generic;

Do I have to use the new keyword for each comparison, or can I just use one instance?

public static IComparer<foo> SortAgeAscending()
        {
            return new SortAgeAscendingHelper();
        }

If you want to have a comparer for the name field and a comparer for the age field, you can either implement 2 comparers , FooAgeComparer and FooNameComparer (for example), or create a single comparer that accepts a function argument in its constructor. Something sort of like this

class Foo
{
    public int Age { get; set; }
    public string Name { get; set; }
}

class FooComparer : IComparer<Foo>
{
    public FooComparer(Func<Foo, Foo, int> comparer)
    {
        this.Comparer = comparer;
    }

    public Func<Foo, Foo, int> Comparer { get; set; }

    public int Compare(Foo x, Foo y)
    {
        return Comparer(x, y);
    }
}

And then you could consume it like this

Foo[] foos = 
{
    new Foo() { Age = 1, Name = "C"},
    new Foo() { Age = 2, Name = "A"},
    new Foo() { Age = 3, Name = "B"}
};

FooComparer comparerOnAge = new FooComparer((x, y) => x.Age.CompareTo(y.Age));
FooComparer comparerOnName = new FooComparer((x, y) => x.Name.CompareTo(y.Name));

Array.Sort(foos, comparerOnAge);
foreach (Foo foo in foos)
    Console.WriteLine(foo.Age);

Array.Sort(foos, comparerOnName);
foreach (Foo foo in foos)
    Console.WriteLine(foo.Name);

With that said, you can actually just use the lambda and skip the comparer implementation, if you desire.

Array.Sort(foos, (x, y) => x.Name.CompareTo(y.Name));
foreach (Foo foo in foos)
    Console.WriteLine(foo.Name);

I see, thank you.

When using lambdas I still have to use the new keyword each time I wish to sort an array?

Therefore if I am sorting the array 60 times a second then is this method still sensible, or is it causing garbage?

Would it be better to define the IComparer as a struct perhaps?

struct SortAgeAscendingHelper: IComparer<Foo>
    {
        public int Compare(Foo a, Foo b)
        {
            if (a.Age> b.Age)
            {
                return 1;
            }
            if (a.Age< b.Age)
            {
                return -1;
            }
            else
            {
                return 0;
            }
        }

        public static IComparer<Foo> SortAgeAscending()
        {
            return new SortAgeAscendingHelper();
        }
    }

Using an actual implementation of the interface would require creating a new object for each type of sort, not necessarily each sort. For example, if you're sorting 100 times, but each time it's sorting on Name, then you only need one instance of your comparer that you would instantiate beforehand. You certainly do not need a new comparer for each sort unless you were sorting on a different property every single time.

FooComparer comparer = new FooComparer(...

// call 1 time, 100 times, 1000000 times, etc
Array.Sort(foos, comparer);

But I as I have also shown, you can merely provide a lambda function and skip the interface implementation altogether.

Thanks apegram.

Will changing the class to a struct for IComparer (should I want to write more specific comparison code) make any difference?

A struct wouldn't make sense here because structs are typically used for representing data of some sort. What's the data value in this instance? No, I would stick with the class. Plus consider that passing around the class (such as when you call the Sort method) is only passing the value of reference of the class, whereas using a struct would be creating a copy of it. So if you have a single comparer class and call sort 100 times, it's still working with the single class. If you have a comparer struct and call sort 100 times, you've copied the entire struct 100 times. Stick with the class.

commented: Stick with the ape's post. +9
Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.