Linq handling variable number of OrderBy - c#

I need to support a variable number of Orderby terms in a Linq (to Entity) statement. That is, my function will accept a list of properties on which the data should be order. The properties can have both ascending or descending sorts. What is the best way to handle constructing the Linq query?
Thanks!

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.

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);
}

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.

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

LINQ select property by name [duplicate]

This question already has answers here:
Dynamic LINQ OrderBy on IEnumerable<T> / IQueryable<T>
(24 answers)
Closed 2 years ago.
The community reviewed whether to reopen this question last year and left it closed:
Original close reason(s) were not resolved
I'm attempting to use a variable inside of a LINQ select statement.
Here is an example of what I'm doing now.
using System;
using System.Collections.Generic;
using System.Linq;
using Faker;
namespace ConsoleTesting
{
internal class Program
{
private static void Main(string[] args)
{
List<Person> listOfPersons = new List<Person>
{
new Person(),
new Person(),
new Person(),
new Person(),
new Person(),
new Person(),
new Person(),
new Person(),
new Person(),
new Person(),
new Person()
};
var firstNames = Person.GetListOfAFirstNames(listOfPersons);
foreach (var item in listOfPersons)
{
Console.WriteLine(item);
}
Console.WriteLine();
Console.ReadKey();
}
public class Person
{
public string City { get; set; }
public string CountryName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Person()
{
FirstName = NameFaker.Name();
LastName = NameFaker.LastName();
City = LocationFaker.City();
CountryName = LocationFaker.Country();
}
public static List<string> GetListOfAFirstNames(IEnumerable<Person> listOfPersons)
{
return listOfPersons.Select(x => x.FirstName).Distinct().OrderBy(x => x).ToList();
}
public static List<string> GetListOfCities(IEnumerable<Person> listOfPersons)
{
return listOfPersons.Select(x => x.FirstName).Distinct().OrderBy(x => x).ToList();
}
public static List<string> GetListOfCountries(IEnumerable<Person> listOfPersons)
{
return listOfPersons.Select(x => x.FirstName).Distinct().OrderBy(x => x).ToList();
}
public static List<string> GetListOfLastNames(IEnumerable<Person> listOfPersons)
{
return listOfPersons.Select(x => x.FirstName).Distinct().OrderBy(x => x).ToList();
}
}
}
}
I have a Some very not DRY code with the GetListOf... Methods
i feel like i should be able to do something like this
public static List<string> GetListOfProperty(
IEnumerable<Person> listOfPersons, string property)
{
return listOfPersons.Select(x =>x.property).Distinct().OrderBy(x=> x).ToList();
}
but that is not vaild code. I think the key Might Relate to Creating a Func
if That is the answer how do I do that?
Here is a second attempt using refelection But this is also a no go.
public static List<string> GetListOfProperty(IEnumerable<Person>
listOfPersons, string property)
{
Person person = new Person();
Type t = person.GetType();
PropertyInfo prop = t.GetProperty(property);
return listOfPersons.Select(prop).Distinct().OrderBy(x =>
x).ToList();
}
I think the refection might be a DeadEnd/red herring but i thought i would show my work anyway.
Note Sample Code is simplified in reality this is used to populate a datalist via AJAX to Create an autocomplete experience. That object has 20+ properties and I can complete by writing 20+ methods but I feel there should be a DRY way to complete this. Also making this one method also would clean up my controller action a bunch also.
Question:
Given the first section of code is there a way to abstract those similar methods into a single method buy passing some object into the select Statement???
Thank you for your time.
You would have to build the select
.Select(x =>x.property).
by hand. Fortunately, it isn't a tricky one since you expect it to always be the same type (string), so:
var x = Expression.Parameter(typeof(Person), "x");
var body = Expression.PropertyOrField(x, property);
var lambda = Expression.Lambda<Func<Person,string>>(body, x);
Then the Select above becomes:
.Select(lambda).
(for LINQ based on IQueryable<T>) or
.Select(lambda.Compile()).
(for LINQ based on IEnumerable<T>).
Note that anything you can do to cache the final form by property would be good.
From your examples, I think what you want is this:
public static List<string> GetListOfProperty(IEnumerable<Person>
listOfPersons, string property)
{
Type t = typeof(Person);
PropertyInfo prop = t.GetProperty(property);
return listOfPersons
.Select(person => (string)prop.GetValue(person))
.Distinct()
.OrderBy(x => x)
.ToList();
}
typeof is a built-in operator in C# that you can "pass" the name of a type to and it will return the corresponding instance of Type. It works at compile-time, not runtime, so it doesn't work like normal functions.
PropertyInfo has a GetValue method that takes an object parameter. The object is which instance of the type to get the property value from. If you are trying to target a static property, use null for that parameter.
GetValue returns an object, which you must cast to the actual type.
person => (string)prop.GetValue(person) is a lamba expression that has a signature like this:
string Foo(Person person) { ... }
If you want this to work with any type of property, make it generic instead of hardcoding string.
public static List<T> GetListOfProperty<T>(IEnumerable<Person>
listOfPersons, string property)
{
Type t = typeof(Person);
PropertyInfo prop = t.GetProperty(property);
return listOfPersons
.Select(person => (T)prop.GetValue(person))
.Distinct()
.OrderBy(x => x)
.ToList();
}
I would stay away from reflection and hard coded strings where possible...
How about defining an extension method that accepts a function selector of T, so that you can handle other types beside string properties
public static List<T> Query<T>(this IEnumerable<Person> instance, Func<Person, T> selector)
{
return instance
.Select(selector)
.Distinct()
.OrderBy(x => x)
.ToList();
}
and imagine that you have a person class that has an id property of type int besides those you already expose
public class Person
{
public int Id { get; set; }
public string City { get; set; }
public string CountryName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
all you need to do is fetch the results with type safe lambda selectors
var ids = listOfPersons.Query(p => p.Id);
var firstNames = listOfPersons.Query(p => p.FirstName);
var lastNames = listOfPersons.Query(p => p.LastName);
var cityNames = listOfPersons.Query(p => p.City);
var countryNames = listOfPersons.Query(p => p.CountryName);
Edit
As it seems you really need hardcoded strings as the property inputs, how about leaving out some dynamism and use a bit of determinism
public static List<string> Query(this IEnumerable<Person> instance, string property)
{
switch (property)
{
case "ids": return instance.Query(p => p.Id.ToString());
case "firstName": return instance.Query(p => p.FirstName);
case "lastName": return instance.Query(p => p.LastName);
case "countryName": return instance.Query(p => p.CountryName);
case "cityName": return instance.Query(p => p.City);
default: throw new Exception($"{property} is not supported");
}
}
and access the desired results as such
var cityNames = listOfPersons.Query("cityName");
You should be able to do it with Reflection. I use it something similar.
Just change your reflection try to this:
public static List<string> GetListOfValues(IEnumerable<Person> listOfPersons, string propertyName)
{
var ret = new List<string>();
PropertyInfo prop = typeof(Person).GetProperty(propertyName);
if (prop != null)
ret = listOfPersons.Select(p => prop.GetValue(p).ToString()).Distinct().OrderBy(x => x).ToList();
return ret;
}
I hope it helps.
It's based on C# 6
You can also use this. works for me.
public static class ObjectReflectionExtensions
{
public static object GetValueByName<T>(this T thisObject, string propertyName)
{
PropertyInfo prop = typeof(T).GetProperty(propertyName);
return prop.GetValue(thisObject);
}
}
And call like this.
public static List<string> GetListOfProperty(IEnumerable<Person> listOfPersons, string propertyName)
{
return listOfPersons.Select(x =>(string)x.GetValueByName(propertyName)).Distinct().OrderBy(x=> x).ToList();
}
If you want to select all the values:
object[] foos = objects.Select(o => o.GetType().GetProperty("PropertyName").GetValue(o)).ToArray();

Multiple expression in LINQ select

I want combine my expression built in runtime (CustomExpression) with ordinary select clausule. Is there any way in C# to do this without manually building whole expression?
var dto = iqueryable.Select(d => new DTO()
{
X = d.X,
Y = d.Y,
Z = CustomExpression
}
Where CustomExpression is something like this:
private Expression<Func<EntityTypeFromIQueryable, string>> CustomExpression() {
get {
// there is manually built expression like this:
return x => x.Blah
}
}
You have to insert some kind of compilable placeholder (like an Extension Method) into your expression first. Then, at runtime, you can modify the expression using an Expression Visitor to replace your "placeholder" with the actual lambda expression. Since your actual expression uses different Parameters (d vs. x) you have to replace them with those of the "original" expression.
In fact, i'm playing around with such scenarios within this project, where i've tried to abstract this kind of expression plumbing. Your "combine" would then look like that:
var dto = iqueryable.ToInjectable().Select(d => new DTO()
{
X = d.X,
Y = d.Y,
Z = d.CustomExpression()
}
public static class CustomExpressions
{
[InjectLambda]
public static string CustomExpression(this EntityTypeFromIQueryable value)
{
// this function is just a placeholder
// you can implement it for non LINQ use too...
throw new NotImplementedException();
}
public static Expression<Func<EntityTypeFromIQueryable, string>> CustomExpression()
{
return x => x.Blah
}
}
The call ToInjectable() creates a lightweight proxy around the original Queryable to modify the expression before execution as described. The attribute InjectLambda marks the "placeholder" as "inject lambda here". By convention the actual expression returned by ToInjectable() gets inserted at the desired position.
You can do it in following way:
static void MultipleExpressionInSelectStatement()
{
List<person> p = new List<person>();
p.Add(new person() { name = "AB", age = 18 });
p.Add(new person() { name = "CD", age = 45 });
var dto = p.Select(d => new person()
{
name=d.name,
age=p.Select(ListExtensions.CustomExpression()).ElementAt(0)
});
}
//customExpression
public static class ListExtensions
{
public static Func<person, int> CustomExpression()
{
return x => x.age;
}
}
//Person Object
public class person
{
public string name { get; set; }
public int age { get; set; }
}

How to perform multiple Linq to Entities orderings dynamically

So my problem is like this; in C# code, I need to perform multiple orderings of an entity set with the help of Linq to Entities, dependent on input parameters. There are three columns to order on, and the order of the ordering columns is itself variable (meaning that for each ordering, I need to look up which column to order on). How can I achieve this?
I have some code that should work, but I repeat myself way too much since I haven't been able to parameterize my operations (Linq to Entities is very restrictive wrt. what I'm allowed to do in my lambdas). Please suggest how I can rewrite my code in accordance with the DRY principle, perhaps with the help of T4 code generation?
The following code should illustrate my problem. It's an excerpt of the real code, for brevity, let me know if I should include more. The orderSpecs variable is an array of "order specifications", each of which specifying a column to order on and whether to order in a descending manner. The orderSpecs array has at least one element, so at least one ordering is performed.
using (var db = new MyContainer())
{
var orderSpec = orderSpecs[0];
IQueryable<DbVersion> dVersions = null;
if (orderSpec.Column == 0)
{
if (orderSpec.Descending)
{
dVersions = db.Versions.OrderByDescending(ver => ver.Name);
}
else
{
dVersions = db.Versions.OrderBy(ver => ver.Name);
}
}
else if (orderSpec.Column == 1)
{
if (orderSpec.Descending)
{
dVersions = db.Versions.OrderByDescending(ver => ver.Built);
}
else
{
dVersions = db.Versions.OrderBy(ver => ver.Built);
}
}
else if (orderSpec.Column == 2)
{
if (orderSpec.Descending)
{
dVersions = db.Versions.OrderByDescending(ver => ver.Id);
}
else
{
dVersions = db.Versions.OrderBy(ver => ver.Id);
}
}
foreach (var spec in orderSpecs.Skip(1))
{
if (spec.Column == 0)
{
if (spec.Descending)
{
dVersions = dVersions.ThenByDescending(ver => ver.Name);
}
else
{
dVersions = dVersions.ThenBy(ver => ver.Name);
}
}
else if (spec.Column == 1)
{
if (spec.Descending)
{
dVersions = dVersions.ThenByDescending(ver => ver.Built);
}
else
{
dVersions = dVersions.ThenBy(ver => ver.Built);
}
}
else if (spec.Column == 2)
{
if (spec.Descending)
{
dVersions = dVersions.ThenByDescending(ver => ver.Id);
}
else
{
dVersions = dVersions.ThenBy(ver => ver.Id);
}
}
}
What about creating a dictionary for mapping these colums that are causing these huge if-else constructs to the properties. Could look like this:
using (var db = new MyContainer())
{
var orderSpec = orderSpecs[0];
IOrderedEnumerable<DbVersion> dVersions;
var mapping = new Dictionary<int, Func<DbVersion, object>>()
{
{ 0, ver => ver.Name },
{ 1, ver => ver.Built },
{ 2, ver => ver.Id }
};
if (orderSpec.Descending)
dVersions = db.Versions.OrderByDescending(mapping[orderSpec.Column]);
else
dVersions = db.Versions.OrderBy(mapping[orderSpec.Column]);
foreach (var spec in orderSpecs.Skip(1))
{
if (spec.Descending)
dVersions = dVersions.ThenByDescending(mapping[spec.Column]);
else
dVersions = dVersions.ThenBy(mapping[spec.Column]);
}
}
For Untype : You can also make use of Dynamic Linq Library : Using the LINQ Dynamic Query Library
OR
Full article : Handle GridView.OnSorting() and create sorting expression dynamically using LINQ
For Typed : You can do dynamic sorting as below which remove the code that you have written
How to do soring on class called person where the columnname and sorting direction is not fix
IEnumerable<Person> persons = GetPersons();
persons = persons.OrderBy(e.SortExpression, e.SortDirection);
Person class
class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
Generic method with expression tree for sorting data
public static IEnumerable<T> OrderBy<T>(this IEnumerable<T> collection,
string columnName, SortDirection direction)
{
ParameterExpression param = Expression.Parameter(typeof(T), "x"); // x
Expression property = Expression.Property(param, columnName); // x.ColumnName
Func<T, object> func = Expression.Lambda<Func<T, object>>( // x => x.ColumnName
Expression.Convert(Expression.Property(param, columnName),
typeof(object)), param).Compile();
Func<IEnumerable<T>, Func<T, object>, IEnumerable<T>> expression =
SortExpressionBuilder<T>.CreateExpression(direction);
IEnumerable<T> sorted = expression(collection, func);
return sorted;
}

LINQ: Sorting on many columns dynamically

I have a query of IQueryable and want to apply sorting to it dynamically, sorting can be on many columns (asc or desc). I've written the following generic function:
private IQueryable<T> ApplySorting<T,U>(IQueryable<T> query, Expression<Func<T, U>> predicate, SortOrder order)
{
if (order == SortOrder.Ascending)
{
{
return query.OrderBy<T, U>(predicate);
}
}
else
{
{
return query.OrderByDescending<T, U>(predicate);
}
}
}
SortOrder is my simple enum with 2 values: Ascending and Descending
Then I call this function in a loop, for each column that user requested sorting. However I've noticed it fails because it always sorts on the last column used, ignoring the other ones.
Then I found there's a 'ThenBy' method on IOrderedQueryable so the valid usage is:
var q = db.MyType.OrderBy(x=>x.Col1).ThenBy(y=>y.Col2); //etc.
But how can I make it generic? I tried to test if the query is IOrderedQueryable but it seems always to be true even if it's simplest var q = from x in db.MyType select x
I have no clue why it was designed like this. What's wrong with:
var q = db.MyType.OrderBy(x=>x.Col1).OrderBy(y=>y.Col2); //etc.
it's so much intuitive
You just need to check if the query is already ordered :
private IQueryable<T> ApplySorting<T,U>(IQueryable<T> query, Expression<Func<T, U>> predicate, SortOrder order)
{
var ordered = query as IOrderedQueryable<T>;
if (order == SortOrder.Ascending)
{
if (ordered != null)
return ordered.ThenBy(predicate);
return query.OrderBy(predicate);
}
else
{
if (ordered != null)
return ordered.ThenByDescending(predicate);
return query.OrderByDescending(predicate);
}
}
How about just making the first OrderBy static, then always ThenBy?
OrderColumn[] columnsToOrderby = getColumnsToOrderby();
IQueryable<T> data = getData();
if(!columnToOrderBy.Any()) { }
else
{
OrderColumn firstColumn = columnsToOrderBy[0];
IOrderedEnumerable<T> orderedData =
firstColumn.Ascending
? data.OrderBy(predicate)
: data.OrderByDescending(predicate);
for (int i = 1; i < columnsToOrderBy.Length; i++)
{
OrderColumn column = columnsToOrderBy[i];
orderedData =
column.Ascending
? orderedData.ThenBy(predicate)
: orderedData.ThenByDescending(predicate);
}
}
Total guess, but can you do something like this?
query.OrderBy(x => 1).ThenBy<T,U>(predicate)
Any syntax errors aside, the idea is to do an OrderBy() that doesn't affect anything, then do the real work in the .ThenBy() method call
I would write a wrapper around and internally use linq extension methods.
var resultList = presentList
.MyOrderBy(x => x.Something)
.MyOrderBY(y => y.SomethingElse)
.MyOrderByDesc(z => z.AnotherThing)
public IQueryable<T> MyOrderBy(IQueryable<T> prevList, Expression<Func<T, U>> predicate) {
return (prevList is IOrderedQueryable<T>)
? query.ThenBy(predicate)
: query.OrderBy(predicate);
}
public IQueryable<T> MyOrderByDesc(IQueryable<T> prevList, Expression<Func<T, U>> predicate) {
return (prevList is IOrderedQueryable<T>)
? query.ThenByDescending(predicate)
: query.OrderByDescending(predicate);
}
PS: I didn't test the code
Extension to dynamic multi-order:
public static class DynamicExtentions
{
public static IEnumerable<T> DynamicOrder<T>(this IEnumerable<T> data, string[] orderings) where T : class
{
var orderedData = data.OrderBy(x => x.GetPropertyDynamic(orderings.First()));
foreach (var nextOrder in orderings.Skip(1))
{
orderedData = orderedData.ThenBy(x => x.GetPropertyDynamic(nextOrder));
}
return orderedData;
}
public static object GetPropertyDynamic<Tobj>(this Tobj self, string propertyName) where Tobj : class
{
var param = Expression.Parameter(typeof(Tobj), "value");
var getter = Expression.Property(param, propertyName);
var boxer = Expression.TypeAs(getter, typeof(object));
var getPropValue = Expression.Lambda<Func<Tobj, object>>(boxer, param).Compile();
return getPropValue(self);
}
}
Example:
var q =(myItemsToSort.Order(["Col1","Col2"]);
Note: not sure any IQueryable provider can translate this

Avoiding code repetition when using LINQ

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
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);
You might try using a generic comparer.
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;
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.

Categories