是否有可能在不使用OrderBy的情况下将IEnumerable转换为IOrderedEnumerable ?
本文关键字:IEnumerable 转换 IOrderedEnumerable 情况下 有可能 OrderBy 是否 | 更新日期: 2023-09-27 17:54:10
假设有一个扩展方法,可以根据SortMethod
enum指定的几种排序类型(即按各种属性排序)对IQueryable进行排序。
public static IOrderedEnumerable<AClass> OrderByX(this IQueryable<AClass> values,
SortMethod? sortMethod)
{
IOrderedEnumerable<AClass> queryRes = null;
switch (sortMethod)
{
case SortMethod.Method1:
queryRes = values.OrderBy(a => a.Property1);
break;
case SortMethod.Method2:
queryRes = values.OrderBy(a => a.Property2);
break;
case null:
queryRes = values.OrderBy(a => a.DefaultProperty);
break;
default:
queryRes = values.OrderBy(a => a.DefaultProperty);
break;
}
return queryRes;
}
在sortMethod
是null
的情况下(即在指定的地方,我不关心值的顺序),是否有一种方法,而不是通过一些默认属性排序,而不是通过"有序"传递IEnumerator
值,而不必执行实际排序?
我想调用这个扩展的能力,然后可能执行一些额外的ThenBy
排序。
对于默认情况,您需要做的就是:
queryRes = values.OrderBy(a => 1);
这实际上是一个noop排序。由于OrderBy执行稳定排序,因此在所选对象相等的情况下将保持原始顺序。请注意,由于这是IQueryable
而不是IEnumerable
,因此查询提供程序可能不会执行稳定的排序。在这种情况下,您需要知道保持顺序是否重要,或者只说"我不在乎结果的顺序是什么,只要我可以在结果上调用ThenBy
"是否合适。
IOrderedEnumerable
实现:
public class NoopOrder<T> : IOrderedEnumerable<T>
{
private IQueryable<T> source;
public NoopOrder(IQueryable<T> source)
{
this.source = source;
}
public IOrderedEnumerable<T> CreateOrderedEnumerable<TKey>(Func<T, TKey> keySelector, IComparer<TKey> comparer, bool descending)
{
if (descending)
{
return source.OrderByDescending(keySelector, comparer);
}
else
{
return source.OrderBy(keySelector, comparer);
}
}
public IEnumerator<T> GetEnumerator()
{
return source.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return source.GetEnumerator();
}
}
你的查询可以是:
queryRes = new NoopOrder<AClass>(values);
注意上述类的结果是,如果调用ThenBy
,则ThenBy
将有效地成为顶级排序。它实际上是将随后的ThenBy
转换为OrderBy
调用。(这不足为奇;ThenBy
将调用CreateOrderedEnumerable
方法,在这里代码调用OrderBy
,基本上把ThenBy
变成了OrderBy
。从概念排序的角度来看,这是在说"这个序列中的所有项在这个排序的眼中都是相等的,但是如果你指定相等的对象应该被其他东西打破,那么就这样做。"
另一种考虑"无op排序"的方式是,它根据输入序列的索引对项排序。这意味着项目并不都是"相等的",这意味着输入序列的顺序将作为输出序列的最终顺序,并且由于输入序列中的每个项目总是比它之前的一个大,添加额外的"决定论"比较将没有任何作用,使得任何后续的ThenBy
调用都没有意义。如果需要这种行为,它甚至比前一种更容易实现:
public class NoopOrder<T> : IOrderedEnumerable<T>
{
private IQueryable<T> source;
public NoopOrder(IQueryable<T> source)
{
this.source = source;
}
public IOrderedEnumerable<T> CreateOrderedEnumerable<TKey>(Func<T, TKey> keySelector, IComparer<TKey> comparer, bool descending)
{
return new NoopOrder<T>(source);
}
public IEnumerator<T> GetEnumerator()
{
return source.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return source.GetEnumerator();
}
}
如果你总是返回相同的索引值,你将得到一个IOrderedEnumerable,保留原来的列表顺序:
case null:
queryRes = values.OrderBy(a => 1);
break;
顺便说一句,我不认为这是一个正确的事情。您将得到一个集合,它应该是有序的,但实际上它不是。
最重要的是,IOrderedEnumerable的存在仅仅是为了向OrderBy()/ThenBy()方法提供语法结构,防止您尝试用ThenBy()开始排序子句。的过程。它不打算成为一个"标记",将集合标识为有序的,除非它实际上是由OrderBy()排序的。因此,答案是,如果排序方法为null应该表明可枚举对象处于某种"默认顺序",则应该指定该默认顺序(正如当前实现所做的那样)。如果说可枚举对象是有序的,而实际上它不是有序的,这是不诚实的,即使通过不指定SortingMethod,您可以推断它是"无排序"的,并且不关心实际的顺序。
试图简单地使用接口将集合标记为有序的固有"问题"是,这个过程不仅仅是简单地排序。通过执行排序方法链,例如myCollection.OrderBy().ThenBy().ThenByDescending()
,您实际上并没有在每次调用时对集合进行排序;至少还没有。相反,您定义了一个名为OrderedEnumerable的"迭代器"类的行为,它将使用您在链中定义的投影和比较,在您需要实际排序的元素时执行排序。
Servy的回答是,OrderBy(x=>1)是一个错误,应该从SQL提供程序中优化,这忽略了这样一个事实,即针对Enumerable进行的调用仍然会做相当多的工作,而且大多数SQL提供程序实际上都没有优化这种调用;在大多数Linq提供程序中,OrderBy(x=>1)将生成一个带有"ORDER BY 1"子句的查询,这不仅迫使SQL提供程序执行自己的排序,而且实际上会导致顺序的更改,因为在T-SQL中至少"ORDER BY 1"意味着按选择列表的第一列排序。