Имеется List<T>
, где T
является каким-то классом с полями. Также имеется класс, содержащий параметры сортировки:
public class Sort
{
public String Property { get; set; }
public String Direction { get; set; }
}
Property
указывает по какому полю будет осуществляться сортировка, а Direction
указывает по возрастанию или убыванию (реально принимает значение "ASC"
или "DSC"
)
Имеется метод, с сигнатурой
public static IEnumerable<T> Sort<T>(IEnumerable<T> items, Sort[] sort)
В массиве Sort[]
чем меньше индекс элемента, тем выше его приоритет.
Хочется понять как можно осуществить сортировку по нескольким полям (больше двух), притом что явно не понятно в каком направлении и какое количество полей будет использоваться. Иными словами нельзя написать
items.OrderBy(...).ThenBy(...)
Для извлечения свойств из типа T
используется typeof(T).GetProperty(sort[i].Property)
Примерно так:
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();
}
можно убрать if-ы и добавить чуть-чуть динамики, вроде поиска метода ThenBy
по имени - но на скорость работы это никак не повлияет.
Этот метод не идеален: он упадет при попытке сравнить по свойствам с типом, не реализующим IComparable
(но, например, реализующим IComparable<T>
). Для обхода проблемы в общем случае придется
IComparable
на object
, получив боксинг при сравненииIQueryable
- свалив проблему построения фунции сравнения на провайдер Linq To Objects, как сделано в ответе Andrei.GetKeySelector
- это просто способ сделать typeof(T).GetProperty(sort[i].Property)
без медленного рефлекшена.
Кажется, это то, что вам нужно:
Сам метод будет выглядеть примерно так:
массив Sort
нужно будет предварительно поменять, первый элемент должен быть OrderBy
(OrderByDescending
), остальные 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();
}
Ещё нужен экстеншен:
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/
Кофе для программистов: как напиток влияет на продуктивность кодеров?
Рекламные вывески: как привлечь внимание и увеличить продажи
Стратегії та тренди в SMM - Технології, що формують майбутнє сьогодні
Выделенный сервер, что это, для чего нужен и какие характеристики важны?
Современные решения для бизнеса: как облачные и виртуальные технологии меняют рынок
Ситуация такая: пишу игру, на подобие match3, те 3 в ряд и ума не приложу как правильно сделать удаление ячейки и появление новых
Модели Good и GoodTag (наследованный от Tag)
Каким образом работает stackpop() ? Хочу реализовать функцию Undo