5

My problem is that I always want to order a collection of objects in a certain fashion.

For example:

class foo{
public string name {get;set;}
public DateTime date {get;set;}
public int counter {get;set;}
}

...

IEnumerable<foo> dosomething(foo[] bar){ 
return bar.OrderBy(a=>a.name).ThenBy(a=>a.date).ThenBy(a=>a.counter);
}

The issue I have is its quite longwinded tacking-on the sort order all the time. A neat solution appears to just create a class that implements IComparer<foo>, meaning I can do:

IEnumerable<foo> dosomething(foo[] bar){ 
return bar.OrderBy(a=>a, new fooIComparer())
}

.

The problem is, the order method this implements is as follows

...

public int Compare(foo x, foo y){ }

Meaning it compares on a very granular basis.

The currently implementation (which will probably work, although im writing pseudocode)

public int Compare(foo x, foo y){
if (x==y)
  return 0;
var order = new []{x,y}.OrderBy(a=>a.name).ThenBy(a=>a.date).ThenBy(a=>a.counter);
  return (order[0] == x) ? -1 : -1;//if x is first in array it is less than y, else it is greater
}

This is not exactly efficient, can another offer a neater solution? Ideally without a Compare(x,y) method altogether?

maxp
  • 24,209
  • 39
  • 123
  • 201
  • 1
    I don't quite understand what the problem is here. Do you have a dynamic collection that should always produce its contents in some specific order? Use e.g. a [`SortedSet`](http://msdn.microsoft.com/en-us/library/dd412070.aspx) with a custom comparer and everything will happen automatically (caveat: `SortedSet` does not allow duplicates). – Jon Feb 20 '13 at 09:54
  • Not to confuse the issue with the `dynamic` keyword, but yes the items in the collection are probably unique/different every time. Im looking at `SortedSet` now... – maxp Feb 20 '13 at 09:57
  • By "dynamic" I mean "stuff is getting into and out of the collection all the time". If it was something you build once and then don't modify you could simply sort once after building and call it a day. – Jon Feb 20 '13 at 10:03

4 Answers4

3

Option 1 - The Comparer

As you're ordering by multiple conditions, you'll to check them individually within each case; for example, if x.name and y.name are equal, then you would check x.date and y.date, and so on.

public class FooComparer : IComparer<Foo>
{
    public int Compare(Foo x, Foo y)
    {
       // nasty null checks!
        if (x == null || y == null)
        {
            return x == y ? 0
                : x == null ? -1
                : 1;
        }

        // if the names are different, compare by name
        if (!string.Equals(x.Name, y.Name))
        {
            return string.Compare(x.Name, y.Name);
        }

        // if the dates are different, compare by date
        if (!DateTime.Equals(x.Date, y.Date))
        {
            return DateTime.Compare(x.Date, y.Date);
        }

        // finally compare by the counter
        return x.Counter.CompareTo(y.Counter);
    }
}

Option 2 - The extension method

An alternative, not so appealing approach, could be an extension method. Sadly as the TKey for each ThenBy can be different, we lose the power of generics, but can safely replace it with the type object in this case.

public static IOrderedEnumerable<T> OrderByThen<T>(this IEnumerable<T> source, Func<T, object> selector, params Func<T, object>[] thenBySelectors)
{
    IOrderedEnumerable<T> ordered = source.OrderBy(selector);
    foreach (Func<T, object> thenBy in thenBySelectors)
    {
        ordered = ordered.ThenBy(thenBy);
    }

    return ordered;
}
Richard
  • 8,110
  • 3
  • 36
  • 59
2

You have to implement IComparable<foo> and compare all properties:

class foo: IComparable<foo>, IComparer<foo>
{
    public string name { get; set; }
    public DateTime date { get; set; }
    public int counter { get; set; }

    public int Compare(foo x, foo y)
    {
        if (x == null || y == null) return int.MinValue;
        if (x.name != y.name)
            return StringComparer.CurrentCulture.Compare(x.name, y.name);
        else if (x.date != y.date)
            return x.date.CompareTo(y.date);
        else if (x.counter != y.counter)
            return x.counter.CompareTo(y.counter);
        else
            return 0;
    }

    public int CompareTo(foo other)
    {
        return Compare(this, other);
    }
}

Then you can use OrderBy in this way:

var ordered = foos.OrderBy(f => f).ToList();
Tim Schmelter
  • 450,073
  • 74
  • 686
  • 939
1

what's wrong with an extension method?

happygilmore
  • 3,008
  • 4
  • 23
  • 37
  • Whilst not a huge fan of extension methods, it totally did not occur to me to use one of these, looking into it now... – maxp Feb 20 '13 at 09:58
1

Why wont you simply compare your values:

int Compare(foo x, foo y)
{

    if (x== null && y == null)
        return 0;
    else if (x == null)
        return -1;
    else if (y == null)
        return 1;

    var nameComparision = string.Compare(x.name,y.name);
    if (nameComparision != 0)
        return nameComparision;
    var dateComparision = x.date.CompareTo(y.date);
    if (dateComparision != 0)
        return dateComparision;
    var counterComparision  = x.counter.CompareTo(y.counter);
    return counterComparision;
}
Rafal
  • 12,391
  • 32
  • 54