There is a List<T> , where T is some kind of class with fields. There is also a class containing collations:

 public class Sort { public String Property { get; set; } public String Direction { get; set; } } 

Property indicates which field will be sorted, and Direction indicates ascending or descending (actually takes the value "ASC" or "DSC" )

There is a method with a signature

 public static IEnumerable<T> Sort<T>(IEnumerable<T> items, Sort[] sort) 

In the Sort[] array, the lower the element index, the higher its priority.

I want to understand how to sort on several fields (more than two), despite the fact that it is clearly not clear in which direction and how many fields will be used. In other words, you can not write

 items.OrderBy(...).ThenBy(...) 

To extract properties from type T , typeof(T).GetProperty(sort[i].Property)

  • Is the field type fixed? If yes, then easier. - VladD
  • Add an example of the implementation of Sort<T> that is now - Grundy
  • @VladD fields are either int, or string, or long - Artur Shaikhutdinov
  • @Grundy current implementation of the Sort<T> method sorted only by the first property from the Sort[] array, so it somehow doesn’t really fit into this question - Artur Shaikhutdinov

2 answers 2

Like that:

 public static IEnumerable<T> Sort<T>(IEnumerable<T> items, Sort[] sort) { IOrderedEnumerable<T> temp = null; foreach (var s in sort) { Func<T, IComparable> keySelector = GetKeySelector<T>(s.Property); if (temp == null) { temp = s.Direction == "Asc" ? items.OrderBy(keySelector) : items.OrderByDescending(keySelector); } else { temp = s.Direction == "Asc" ? temp.ThenBy(keySelector) : temp.ThenByDescending(keySelector); } } return temp ?? items; } private static Func<T, IComparable> GetKeySelector<T>(string property) { var param = Expression.Parameter(typeof(T)); var lambda = Expression.Lambda<Func<T, IComparable>>( Expression.Convert( Expression.Property(param, property), typeof(IComparable)), param); return lambda.Compile(); } 

you can remove the ThenBy and add a bit of dynamics, like finding the ThenBy method by name - but this will not affect the speed of work.

This method is not perfect: it will fall when trying to compare properties with a type that does not implement IComparable (but, for example, implements IComparable<T> ). To work around the problem, in general, you will

  • or replace in the IComparable code with object , receiving boxing when comparing
  • or switch to IQueryable - by IQueryable problem of building a comparison function to the Linq To Objects provider, as was done in Andrei’s answer

GetKeySelector is just a way to do typeof(T).GetProperty(sort[i].Property) without slow reflection.

  • +1, but: what if the custom type does not implement IComparable ? Has the right to. Do not fly out on this? - VladD
  • @VladD then the sorter, like the standard OrderBy, has exactly the same right to pick up and fall :) how can OrderBy fly out, and custom sorting is impossible? - PashaPash
  • Does the standard sorter seem to do not castes, but use Comparer<T>.Default ? - VladD
  • one
    @VladD ... which successfully throws an exception At least one object must implement IComparable. when trying to compare through it not IComparable - PashaPash
  • one
    As far as I remember, he first uses IComparable<T> if he finds one. That is, if the type implements IComparable<T> , but not IComparable , your code will IComparable , and the default will not. - VladD

It seems that this is what you need:

The method itself will look something like this:

Sort array will need to be changed beforehand, the first element must be OrderByDescending ( OrderByDescending ), the rest are ThenBy ( ThenByDescending )

  public static IEnumerable<T> SortIt<T>(IEnumerable<T> items, Sort[] sorts) { var queryItems = items.AsQueryable(); foreach (var s in sorts) queryItems = queryItems.ApplyOrder(s.Property, s.Direction); return queryItems.AsEnumerable(); } 

Still need extension:

  public static class Extensions { public static IOrderedQueryable<T> ApplyOrder<T>( this IQueryable<T> source, string property, string methodName ) { var arg = Expression.Parameter(typeof(T), "x"); Expression expr = arg; expr = Expression.Property(expr, property); var lambda = Expression.Lambda(expr, arg); var method = typeof(Queryable).GetGenericMethod( methodName, new[] { typeof(T), expr.Type }, new[] { source.GetType(), lambda.GetType() } ); return (IOrderedQueryable<T>)method.Invoke(null, new object[] { source, lambda }); } public static MethodInfo GetGenericMethod( this Type type, string name, Type[] genericTypeArgs, Type[] paramTypes ) { var methods = from abstractGenericMethod in type.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static) where abstractGenericMethod.Name == name where abstractGenericMethod.IsGenericMethod let pa = abstractGenericMethod.GetParameters() where pa.Length == paramTypes.Length select abstractGenericMethod.MakeGenericMethod(genericTypeArgs) into concreteGenericMethod where concreteGenericMethod.GetParameters() .Select(p => p.ParameterType).SequenceEqual(paramTypes, new TestAssignable()) select concreteGenericMethod; return methods.FirstOrDefault(); } private class TestAssignable : IEqualityComparer<Type> { public bool Equals(Type x, Type y) { return x.IsAssignableFrom(y); } public int GetHashCode(Type obj) { return obj.GetHashCode(); } } } 

https://habrahabr.ru/post/181065/