如何将 LINQ 的 OrderBy 方向指定为布尔值?

问题描述 投票:0回答:6

我有一个具有以下签名的简单数据类:

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

我希望能够根据字段(指定为

string sortField
)和方向(指定为
bool isAscending
)对这些数据进行排序

目前我正在使用

switch
(每种情况下的升序逻辑为
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;
}

对于 2 个属性来说,这非常丑陋,但是当排序逻辑不同时,它就会成为一个问题(我们还看到

s => s.Number
在代码中重复了两次)

问题 传递布尔值来选择排序方向的最佳方法是什么?

我尝试过的事情 我拆开了 System.Core.dll 并找到了 OrderBy Extension 方法实现:

订购者:

public static IOrderedEnumerable<TSource> OrderBy<TSource, TKey>(
        this IEnumerable<TSource> source, 
        Func<TSource, TKey> keySelector
    ){

    return new OrderedEnumerable<TSource, TKey>(
        source, 
        keySelector, 
        null, 
        false
    );
}

按降序排列:

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

看来拥有两个命名方法的目的是抽象这个布尔值。我无法轻松创建自己的扩展,因为

OrderedEnumberable
是 System.Core 的内部组件,并且编写一个从 bool -> methodName -> bool 的层对我来说似乎是错误的。

c# linq
6个回答
20
投票

我想说编写你自己的扩展方法:

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

然后你可以写:

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

至于指定方法名称:我希望这不会成为逃避的答案,但我认为您应该坚持使用选择器函数而不是传入字符串。使用字符串路径并不能真正节省您的任何打字或提高清晰度(

"letter"
真的比
s => s.Letter
更快或更清晰吗?)并且只会使您的代码更胖(您要么需要维护某种映射字符串到选择器函数或编写自定义解析逻辑以在它们之间进行转换)并且可能更脆弱(如果您选择后一条路线,则出现错误的可能性相当高)。

如果您的目的是从用户输入中获取字符串来自定义排序,当然,您别无选择,因此请随意忽略我令人沮丧的言论!


编辑:既然你正在接受用户输入,这就是我所说的映射的意思:

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

上面的显然不如从字符串动态生成表达式并调用它们那么聪明;这可以被视为优势或劣势,具体取决于您的偏好以及您所属的团队和文化。在这种特殊情况下,我想我会投票支持手动映射,因为动态表达路线

感觉过度设计。


3
投票
如果您想在末尾添加更多方法,最好返回 IOrderedEnumerable。通过这种方式,编译器会将整个链编译为一个表达式。

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

1
投票
您可以实现自己的扩展,该扩展将按字符串属性排序并支持通过布尔值升序和降序,例如:

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

0
投票
您可以制作一个

Func

来选择正确的操作:

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

按照 CraftyFella 的建议,与

动态 LINQ 结合,它可能看起来像:

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

或者,如果您愿意,也可以排长队:

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

我想我更喜欢丹涛的解决方案而不是我的,只是想我会把它扔在那里以防你发现它有用。


0
投票
我想看看 ScottGu

这里描述的动态 linq 选项


0
投票
var query = _applicationDbContext.Conversations.AsQueryable(); if (sortDirection == "asc") query = query.OrderBy(x => EF.Property<object>(x, sortColumn)); else query = query.OrderByDescending(x => EF.Property<object>(x, sortColumn));
    
© www.soinside.com 2019 - 2024. All rights reserved.