Страницы

Поиск по вопросам

суббота, 7 декабря 2019 г.

Сортировка List<T> по нескольким полям c учётом приоритета

#c_sharp #сортировка


Имеется List, где T является каким-то классом с полями. Также имеется класс, содержащий
параметры сортировки:

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


Property указывает по какому полю будет осуществляться сортировка, а Direction указывает
по возрастанию или убыванию (реально принимает значение "ASC" или "DSC")

Имеется метод, с сигнатурой

public static IEnumerable Sort(IEnumerable items, Sort[] sort)


В массиве Sort[] чем меньше индекс элемента, тем выше его приоритет.

Хочется понять как можно осуществить сортировку по нескольким полям (больше двух),
притом что явно не понятно в каком направлении и какое количество полей будет использоваться.
Иными словами нельзя написать 

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


Для извлечения свойств из типа T используется typeof(T).GetProperty(sort[i].Property)
    


Ответы

Ответ 1



Примерно так: public static IEnumerable Sort(IEnumerable items, Sort[] sort) { IOrderedEnumerable temp = null; foreach (var s in sort) { Func keySelector = GetKeySelector(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 GetKeySelector(string property) { var param = Expression.Parameter(typeof(T)); var lambda = Expression.Lambda>( Expression.Convert( Expression.Property(param, property), typeof(IComparable)), param); return lambda.Compile(); } можно убрать if-ы и добавить чуть-чуть динамики, вроде поиска метода ThenBy по имени - но на скорость работы это никак не повлияет. Этот метод не идеален: он упадет при попытке сравнить по свойствам с типом, не реализующим IComparable (но, например, реализующим IComparable). Для обхода проблемы в общем случае придется или заменить в коде IComparable на object, получив боксинг при сравнении или перейти на IQueryable - свалив проблему построения фунции сравнения на провайдер Linq To Objects, как сделано в ответе Andrei. GetKeySelector - это просто способ сделать typeof(T).GetProperty(sort[i].Property) без медленного рефлекшена.

Ответ 2



Кажется, это то, что вам нужно: Сам метод будет выглядеть примерно так: массив Sort нужно будет предварительно поменять, первый элемент должен быть OrderBy(OrderByDescending), остальные ThenBy(ThenByDescending) public static IEnumerable SortIt(IEnumerable 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 ApplyOrder( this IQueryable 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)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 { public bool Equals(Type x, Type y) { return x.IsAssignableFrom(y); } public int GetHashCode(Type obj) { return obj.GetHashCode(); } } } https://habrahabr.ru/post/181065/

Комментариев нет:

Отправить комментарий