C# 如何指定LINQ';s OrderBy direction作为布尔值?

C# 如何指定LINQ';s OrderBy direction作为布尔值?,c#,linq,C#,Linq,我有一个简单的数据类,具有以下签名: internal interface IMyClass { string Letter { get; } int Number { get; } } 我希望能够根据字段(指定为string sortField)和方向(指定为bool isAscending)对这些数据进行排序 目前我正在使用一个开关(每种情况下的升序逻辑为if) OrderByDescending: public static IOrderedEnumerable<T

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

internal interface IMyClass {
    string Letter { get; }
    int Number { get; }
}
我希望能够根据字段(指定为
string sortField
)和方向(指定为
bool isAscending
)对这些数据进行排序

目前我正在使用一个
开关
(每种情况下的升序逻辑为
if

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
        );
}
公共静态IOrderedEnumerable OrderByDescending(
这是一个数不清的来源,
Func键选择器
){
返回新OrderedEnumerable(
来源:,
按键选择器,
无效的
真的
);
}

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

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

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);
    }
}
至于指定方法名:我希望这不会成为一个逃避的答案,但我认为您应该坚持使用选择器函数,而不是传递字符串。使用字符串路径并不会真正节省键入时间或提高清晰度(是不是“字母”确实比
s=>s.letter
?)快得多或更清晰),只会使代码更胖(您需要维护从字符串到选择器函数的某种映射,或者编写自定义解析逻辑在它们之间进行转换)而且可能更脆弱(如果你选择后一种方式,那么出现bug的可能性相当高)

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


编辑:由于您接受用户输入,以下是我所说的映射:

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);
    }
}
class客户分拣机
{
静态字典选择器;
静态CustomSorter()
{
选择器=新字典
{
{“字母”,新函数(x=>x.letter)},
{“number”,新函数(x=>x.number)}
};
}
公共void排序(IEnumerable列表、字符串sortField、bool isAscending)
{
Func选择器;
if(!Selectors.TryGetValue(sortField,out selector))
{
抛出新ArgumentException(string.Format(“{0}”不是有效的排序字段。”,sortField));
}
//使用上面定义的扩展方法。
返回列表。顺序(选择器,isAscending);
}
}

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

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

如果您想在最后添加更多方法,最好返回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);
    }
}
公共静态类OrderByWithBooleanExtension
{
公共静态IOrderedEnumerable OrderBy(此IEnumerable源、Func键选择器、布尔isAscending)
{
返回isAscending?source.OrderBy(keySelector):source.OrderByDescending(keySelector);
}
}

您可以创建一个选择正确操作的
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;
}
var orderBy=isAscending?(Func)lst.OrderBy:lst.OrderByDescending;
开关(索特菲尔德)
{
案例“信函”:
lst=订购人(s=>s.Letter);
打破
案例“编号”:
lst=订购者(s=>s.Number);
打破
}
结合CraftyFella的建议,它可能看起来像:

var orderBy = isAscending ? (Func<Func<IMyClass, object>, IOrderedEnumerable<IMyClass>>)lst.OrderBy : lst.OrderByDescending;
lst = orderBy(mySortCriteria);
var orderBy=isAscending?(Func)lst.OrderBy:lst.OrderByDescending;
lst=订购者(mySortCriteria);
或者,如果您愿意,可以排一长队:

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

我想我更喜欢Dan Tao的解决方案,只是想如果你觉得有用的话,我会把它扔出去。

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

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))
    );
}
公共静态IOrderedQueryable OrderByProperty(此IQueryable查询,字符串memberName,bool升序=true) { var typeParams=new[]{Expression.Parameter(typeof(T),“”)}; var pi=typeof(T).GetProperty(memberName); 字符串操作=升序?“OrderBy”:“OrderByDescending”; 返回(IOrderedQueryable)query.Provider.CreateQuery( 表情,打电话( 类型(可查询), 活动 新[]{typeof(T),pi.PropertyType}, query.Expression, Expression.Lambda(Expression.Property(typeParams[0],pi),typeParams)) ); }
解决您的编辑问题:之所以它是字符串字段,是因为它来自用户输入(经过消毒)@James:Ha,就是这样计算的(注意在您留下评论之前对我的问题的最后一次编辑!)。好吧,无论如何,我建议在这种情况下使用映射路径,而不是将字符串解析为表达式。但这是你的决定。
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);
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))
    );
}