views:

1725

answers:

4

Ok so I have a number of methods that look like this:- which sorts a list by artist, album, year etc.

        public void SortByAlbum(SortOrder sortOrder)
        {
           if (sortOrder == SortOrder.Ascending)
              _list = _list.OrderBy(x => x.Album).ToList();
           else if (sortOrder == SortOrder.Descending)
              _list = _list.OrderByDescending(x => x.Album).ToList();
        }

and this:

        public void SortByArtist(SortOrder sortOrder)
        {
           if (sortOrder == SortOrder.Ascending)
              _list = _list.OrderBy(x => x.Artist).ToList();
           else if (sortOrder == SortOrder.Descending)
              _list = _list.OrderByDescending(x => x.Artist).ToList();
        }

Now obviously this isn't good code so it needs refactoring into one Sort() method but I just cant figure out how to do it in the simplest possible way. I don't care if it uses IComparer or LINQ.

I want it to look something like this:

    public void Sort(SortOrder sortOrder, SortType sortType)
    {
        //implementation here
    }

    public enum SortType
    {
       Artist,
       Album,
       Year
    }

So whats the cleanest way to do this with no code repetition?

Thanks, Lee

+8  A: 

You should be able to mimick the signature of the OrderBy extension method:

Update 1 you have to be explicit in the first generic parameter to your keySelector Func. I'm going to take a guess at your type and call it "Song".

public void Sort<TKey>(SortOrder sortOrder,
                       Func<Song, TKey> keySelector)
{
    if (sortOrder == SortOrder.Descending)
    {
        _list = _list.OrderByDescending(keySelector).ToList(); 
    }
    else
    {
        _list = _list.OrderBy(keySelector).ToList(); 
    }
}

Now you can call "Sort" like this:

Sort(SortOrder.Descending, x => x.Album);

Update 2

Following up on Tom Lokhorst's comment: If you want to predefine some shorthand sort criteria, you could do so by defining a class like this:

public static class SortColumn
{
    public static readonly Func<Song, string> Artist = x => x.Artist;
    public static readonly Func<Song, string> Album = x => x.Album;
}

Now you can simply call:

Sort(SortOrder.Descending, SortColumn.Artist);
Matt Hamilton
Also, if you, for some reason, don't want to write down the explicit lambda in the call to `Sort` (it could get big), you can create some sort of list with predefined `Func<TSource, TKey>` objects (equivalent to the enum in the example).
Tom Lokhorst
Funny I read that just this morning in the Linq in Action book
Preet Sangha
The type arguments for method 'System.Linq.Enumerable.OrderByDescending<TSource,TKey>(System.Collections.Generic.IEnumerable<TSource>, System.Func<TSource,TKey>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.On the orderby and orderbyDesc lines.
Lee Treveil
@Lee - is that before or after my edit a few minutes ago? I changed my code after testing it and discovering the same error.
Matt Hamilton
Yeah posted that before I read your update, i've fixed the error and its working great. Thanks Matt.
Lee Treveil
How close was I with "Song" as the data type name? :)
Matt Hamilton
+1  A: 

You might try using a generic comparer.

davogones
A: 

Sounds like sorting is taking on a life of its own if you have several methods dedicated to it. Maybe they can be gathered into a class.

public enum SortOrder
{
    Ascending = 0,
    Descending = 1
}
public class Sorter<T>
{
    public SortOrder Direction { get; set; }
    public Func<T, object> Target { get; set; }
    public Sorter<T> NextSort { get; set; }

    public IOrderedEnumerable<T> ApplySorting(IEnumerable<T> source)
    {
        IOrderedEnumerable<T> result = Direction == SortOrder.Descending ?
            source.OrderByDescending(Target) : 
            source.OrderBy(Target);

        if (NextSort != null)
        {
            result = NextSort.ApplyNextSorting(result);
        }
        return result;
    }

    private IOrderedEnumerable<T> ApplyNextSorting
        (IOrderedEnumerable<T> source)
    {
        IOrderedEnumerable<T> result = Direction == SortOrder.Descending ?
            source.ThenByDescending(Target) :
            source.ThenBy(Target);
        return result;
    }
}


Here's sample usage:

List<string> source = new List<string>()
    { "John", "Paul", "George", "Ringo" };

Sorter<string> mySorter = new Sorter<string>()
{
    Target = s => s.Length,
    NextSort = new Sorter<string>()
    {
        Direction = SortOrder.Descending,
        Target = s => s
    }
};

foreach (string s in mySorter.ApplySorting(source))
{
    Console.WriteLine(s);
}

Output is Paul, John, Ringo, George.

David B
A: 

I think you should add an extension method to IList<T>:

 public static class extIList {
    public static IList<T> Sort<T, TKey>(this IList<T> list, SortOrder sortOrder, Func<T, TKey> keySelector) {
            if (sortOrder == SortOrder.Descending) {
                return list.OrderByDescending(keySelector).ToList();
            } else {
                return list.OrderBy(keySelector).ToList();
            }
    }
}

and then you can use pretty with every your objects:

IList<Person> list = new List<Person>();

list.Add(new Person("David","Beckham"));
list.Add(new Person("Gennaro","Gattuso"));
list.Add(new Person("Cristian","Carlesso"));

list = list.Sort(SortOrder.Descending, X => X.Name);

ps SortOrder already exists:

using System.Data.SqlClient;
kentaromiura