提问人:James 提问时间:8/3/2012 更新时间:8/3/2012 访问量:7314
如何将 LINQ 的 OrderBy 方向指定为布尔值?
How do I specify LINQ's OrderBy direction as a boolean?
问:
我有一个简单的数据类,它有这个签名:
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
);
}
似乎拥有 2 个命名方法的目的是将这个布尔值抽象出来。我无法像 System.Core 内部那样轻松创建自己的扩展,并且编写一个从 bool -> methodName -> bool 的层对我来说似乎是错误的。OrderedEnumberable
答:
我会说写你自己的扩展方法:
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);
}
}
以上显然不如从字符串动态生成表达式并调用它们那么聪明;这可以被认为是一种优势或劣势,这取决于你的偏好以及你所属的团队和文化。在这种特殊情况下,我想我会投票支持手动映射,因为动态表达式路由感觉过度设计了。
评论
我会看一下 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);
}
}
您可以创建一个选择正确操作的操作: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 的解决方案而不是我的解决方案,只是想我会把它扔在那里,以防你觉得它有用。
您可以实现自己的扩展,该扩展将按字符串属性排序,并支持通过布尔值升序和降序,例如:
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))
);
}
评论