How to specify LINQ OrderBy direction as logical?

I have a simple data class that has this signature:

internal interface IMyClass { string Letter { get; } int Number { get; } } 

I want to be able to sort this data based on a field (specified as string sortField ) and direction (specified as bool isAscending )

I am currently using switch (with upstream logic in each case as if )

 IEnumerable<IMyClass> lst = new IMyClass[];//provided as paramater switch (sortField) { case "letter": if( isAscending ) { lst = lst.OrderBy( s => s.Letter ); } else { lst = lst.OrderByDescending( s => s.Letter ); } break; case "number": if( isAscending ) { lst = lst.OrderBy( s => s.Number ); } else { lst = lst.OrderByDescending( s => s.Number ); } break; } 

This is pretty ugly for 2 properties, but when the sorting logic is different, it becomes a problem (we also see that s => s.Number duplicated twice in the code)

Question What is the best way to pass a boolean to select a sort direction?

What I tried I tore System.Core.dll and found implementations of the OrderBy extension method:

OrderBy:

 public static IOrderedEnumerable<TSource> OrderBy<TSource, TKey>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector ){ return new OrderedEnumerable<TSource, TKey>( source, keySelector, null, false ); } 

OrderByDescending:

 public static IOrderedEnumerable<TSource> OrderByDescending<TSource, TKey>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector ){ return new OrderedEnumerable<TSource, TKey>( source, keySelector, null, true ); } 

It seems like the goal of having 2 named methods is to distract this Boolean. I cannot easily create my own extension, because OrderedEnumberable is internal to System.Core, and writing the layer to go from bool β†’ methodName β†’ bool seems wrong to me.

+6
source share
5 answers

I would say write your own extension method:

 public static IEnumerable<T> Order<T, TKey>(this IEnumerable<T> source, Func<T, TKey> selector, bool ascending) { if (ascending) { return source.OrderBy(selector); } else { return source.OrderByDescending(selector); } } 

Then you can simply write:

 lst = lst.Order( s => s.Letter, isAscending ); 

As for specifying the method name: I hope this does not come off the answer to copy, but I think you should use the select function instead of passing in a string. Going along a string route doesn't really allow you to type or improve clarity (is the "letter" really much faster or clearer than s => s.Letter ?), And only makes your code thicker (do you need to either support some kind of mapping from strings for selection functions or write your own parsing logic for converting between them) and perhaps more fragile (if you go through the last route, there will be a rather high probability of errors).

If you intend to take a string from user input to set up sorting, of course, you have no choice, so feel free to ignore my disappointing comments!


Change Since you are accepting user input, here is what I mean by matching:

 class CustomSorter { static Dictionary<string, Func<IMyClass, object>> Selectors; static CustomSorter() { Selectors = new Dictionary<string, Func<IMyClass, object>> { { "letter", new Func<IMyClass, object>(x => x.Letter) }, { "number", new Func<IMyClass, object>(x => x.Number) } }; } public void Sort(IEnumerable<IMyClass> list, string sortField, bool isAscending) { Func<IMyClass, object> selector; if (!Selectors.TryGetValue(sortField, out selector)) { throw new ArgumentException(string.Format("'{0}' is not a valid sort field.", sortField)); } // Using extension method defined above. return list.Order(selector, isAscending); } } 

The foregoing is clearly not as smart as dynamically generating expressions from strings and calling them; and this can be considered strength or weakness depending on your preferences, as well as the team and culture of which you are a part. In this particular case, I think I would vote for a manual display, because the dynamic expression route feels overly designed.

+15
source

I would take a look at the dynamic linq options described here from ScottGu

+1
source

You can implement your own extension, which will be ordered using the string property and support ascending and descending through a boolean value, for example:

 public static IOrderedQueryable<T> OrderByProperty<T>(this IQueryable<T> query, string memberName, bool ascending = true) { var typeParams = new[] { Expression.Parameter(typeof(T), "") }; var pi = typeof(T).GetProperty(memberName); string operation = ascending ? "OrderBy" : "OrderByDescending"; return (IOrderedQueryable<T>)query.Provider.CreateQuery( Expression.Call( typeof(Queryable), operation, new[] { typeof(T), pi.PropertyType }, query.Expression, Expression.Lambda(Expression.Property(typeParams[0], pi), typeParams)) ); } 
+1
source

It is better to return an IOrderedEnumerable if you want to add additional methods to the end. Thus, the compiler will compile the whole chain as one of the expressions.

 public static class OrderByWithBooleanExtension { public static IOrderedEnumerable<TSource> OrderBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, bool isAscending) { return isAscending ? source.OrderBy(keySelector) : source.OrderByDescending(keySelector); } } 
0
source

You can make Func , which selects the correct operation:

 var orderBy = isAscending ? (Func<Func<IMyClass, object>, IOrderedEnumerable<IMyClass>>)lst.OrderBy : lst.OrderByDescending; switch (sortField) { case "letter": lst = orderBy(s => s.Letter); break; case "number": lst = orderBy(s => s.Number); break; } 

Combined with dynamic LINQ , as suggested by CraftyFella, it might look like this:

 var orderBy = isAscending ? (Func<Func<IMyClass, object>, IOrderedEnumerable<IMyClass>>)lst.OrderBy : lst.OrderByDescending; lst = orderBy(mySortCriteria); 

Or, if you want, one long line:

 lst = (isAscending ? (Func<Func<IMyClass, object>, IOrderedEnumerable<IMyClass>>)lst.OrderBy : lst.OrderByDescending)(mySortCriteria); 

I think I prefer Dan Tao's solution to mine, I just thought I'd throw it there if you find it useful.

0
source

Source: https://habr.com/ru/post/921966/


All Articles