9

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

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

看起来拥有 2 个命名方法的目的是抽象出这个布尔值。我无法像 System.Core 内部那样轻松创建自己的扩展OrderedEnumberable,并且编写一个从 bool -> methodName -> bool 开始的层对我来说似乎是错误的。

4

5 回答 5

18

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

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

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

于 2012-08-02T16:41:55.747 回答
2

如果您想在末尾添加更多方法,最好返回 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);
    }
}
于 2012-08-02T16:45:32.767 回答
1

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

于 2012-08-02T16:44:21.283 回答
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))
    );
}
于 2012-08-02T16:58:21.377 回答
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);

我想我更喜欢 Dan Tao 的解决方案,只是想我会把它扔在那里,以防你觉得它有用。

于 2012-08-02T16:52:12.960 回答