如何指定LINQ';s的OrderBy方向作为布尔值

本文关键字:方向 OrderBy 布尔值 LINQ 何指定 | 更新日期: 2024-09-21 05:11:12

我有一个简单的数据类,它有这个签名:

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

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

似乎有两个命名方法的目的是将这个布尔值抽象掉。我无法轻松创建自己的扩展,因为OrderedEnumberable是System.Core内部的,编写一个从bool->methodName->bool开始的层对我来说似乎是错误的。

如何指定LINQ';s的OrderBy方向作为布尔值

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

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

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

如果您想在末尾添加更多方法,最好返回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);
    }
}

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

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

您可以制作一个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);

我想我更喜欢丹涛的解决方案,只是想我会把它扔出去,以防你觉得有用。

我会看看ScottGu 在这里描述的动态linq选项