score:2

To sort by an arbitrary property, you need to build an expression tree to pass to OrderBy.

To sort by an arbitrary number of properties, you need to call ThenBy in a loop.

score:9

You should be able to do something along these lines:

public IEnumerable<MyType> DoSomething(params Expression<Func<MyType,object>>[] properties)
 {
     var query = // create LINQ query that returns IQueryable<MyType>
     query = query.OrderBy(properties.First());

     foreach (var property in properties.Skip(1))
     {
         query = query.ThenBy(property);
     }
 }

 …

 var results = DoSomething(() => x.Age, () => x.Height, () => x.LastName);

You'd need to handle the case where fewer than 2 properties are specified.

score:3

Following on from Jay's answer, this can be made into a nice extension method:

public static class EnumerableExtensions
{
    public static IEnumerable<T> OrderByMany<T>(this IEnumerable<T> enumerable, 
        params Expression<Func<T, object>>[] expressions)
    {
        if (expressions.Length == 1)
            return enumerable.OrderBy(expressions[0].Compile());

        var query = enumerable.OrderBy(expressions[0].Compile());
        for (int i = 1; i < expressions.Length;i++)
        {
            query = query.ThenBy(expressions[i].Compile());
        }
        return query;

    }
}

Usage becomes quite simple, given a test object:

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

This is then possible:

var people = new Person[]
                    {
                        new Person() {Name = "John", Age = 40},
                        new Person() {Name = "John", Age = 20},
                        new Person() {Name = "Agnes", Age = 11}
                    };

foreach(var per in  people.OrderByMany(x => x.Name, x => x.Age))
{
    Console.WriteLine("{0} Age={1}",per.Name,per.Age);
}

Output:

Agnes Age=11
John Age=20
John Age=40

UPDATE

You could add another overload of the OrderByMany method to support SortOrder as well, although it gets clunky rather quickly. Personally I'd just go for the syntax

var query = from p 
            in people
            order by Name, Age descending;

However, for the record, in C#4 at least, I would accomplish the overload using an enum & tuple.

public enum SortOrder
{
    Ascending, 
    Descending
}

and the extra overload:

public static IEnumerable<T> OrderByMany<T>(this IEnumerable<T> enumerable,
    params Tuple<Expression<Func<T, object>>,SortOrder>[] expressions)
{

    var query = (expressions[0].Item2 == SortOrder.Ascending)
                    ? enumerable.OrderBy(expressions[0].Item1.Compile())
                    : enumerable.OrderByDescending(expressions[0].Item1.Compile());

    for (int i = 1; i < expressions.Length; i++)
    {
        query = expressions[i].Item2 == SortOrder.Ascending
                    ? query.ThenBy(expressions[i].Item1.Compile())
                    : query.ThenByDescending(expressions[i].Item1.Compile());
    }
    return query;

}

Usage becomes clumsy and hard to read:

foreach (var per in people.OrderByMany(
                    new Tuple<Expression<Func<Person, object>>, SortOrder>(x => x.Age, SortOrder.Descending), 
                    new Tuple<Expression<Func<Person, object>>, SortOrder>(x => x.Name, SortOrder.Ascending)))
{
    Console.WriteLine("{0} Age={1}", per.Name, per.Age);
}

score:0

I like Jamiec's idea but I hate using Tuples because the syntax is ugly. Therefore I built a small class that encapsulates the Tuple and exposes getters for the Item1 and Item2 properties with better variable names.

Also notice that I used a default sort order of ascending so you only need to specify a SortOrder if you want to sort in descending order.

public class SortExpression<T>
{
    private Tuple<Expression<Func<T, object>>, SortOrder> tuple;        

    public SortExpression( Expression<Func<T, object>> expression, SortOrder order =SortOrder.Ascending )
    {
        tuple = new Tuple<Expression<Func<T,object>>, SortOrder>(expression, order);
    }

    public Expression<Func<T, object>> Expression {
        get { return tuple.Item1; }
    }

    public SortOrder Order {
        get { return tuple.Item2; }
    }
}

In my specific application, I have a repository base class which takes an IQueryable and converts it to a ObservableCollection. In that method I use the SortExpression class:

public ObservableCollection<T> GetCollection(params SortExpression<T>[] sortExpressions) {
    var list = new ObservableCollection<T>();
    var query = FindAll();

    if (!sortExpressions.Any()) {
        query.ToList().ForEach(list.Add);
        return list;
    }

    var ordered = (sortExpressions[0].Order == SortOrder.Ascending)
        ? query.OrderBy(sortExpressions[0].Expression.Compile())
        : query.OrderByDescending(sortExpressions[0].Expression.Compile());

    for (var i = 1; i < sortExpressions.Length; i++) {
        ordered = sortExpressions[i].Order == SortOrder.Ascending
            ? ordered.ThenBy(sortExpressions[i].Expression.Compile())
            : ordered.ThenByDescending(sortExpressions[i].Expression.Compile());
    }

    ordered.ToList().ForEach(list.Add);
    return list;
}        

Here is the method in use:

var repository = new ContactRepository(UnitOfWork);
return repository.GetCollection(
                    new SortExpression<Contact>(x => x.FirstName),
                    new SortExpression<Contact>(x => x.LastName));       

Related Articles