我已经简化了问题。我有一个有序的IEnumerable,我想知道为什么应用where过滤器可以对对象进行排序
虽然它应该具有
的潜力,但这不会编译IOrderedEnumerable<int> tmp = new List<int>().OrderBy(x => x);
//Error Cannot Implicitly conver IEnumerable<int> To IOrderedEnumerable<int>
tmp = tmp.Where(x => x > 1);
据我所知,如果来自IQueryable,例如使用linq到某个DB Provider,就没有gaurenteed执行顺序。
然而,在处理Linq To Object时会发生什么样的事件会导致您的对象无序,或者为什么没有实现?
修改
我理解如何正确地订购这不是问题。我的问题更多的是一个设计问题。对linq到对象的where过滤器应枚举给定枚举和应用过滤。那么为什么我们只能返回IEnumerable而不是IOrderedEnumerable?
修改
澄清当这个用户有效的时候。我根据代码中的条件构建查询,我希望尽可能多地重用代码。我有一个函数返回一个OrderedEnumerable,但是在应用了额外的地方后,我将不得不重新排序,即使它将处于其原始的有序状态
答案 0 :(得分:14)
Rene的回答是正确的,但可以使用一些额外的解释。
IOrderedEnumerable<T>
并不意味着“这是一个有序的序列”。这意味着“这是一个已经对其应用订购操作的序列,您现在可以使用ThenBy
来强制执行其他订购要求。”
Where
的结果不允许您使用ThenBy
进行跟进,因此您不能在需要IOrderedEnumerable<T>
的情况下使用它。
有意义吗?
但是,当然,正如其他人所说,你几乎总是希望先进行过滤然后再进行排序。这样你就不会花时间将物品整理成你要丢弃的物品。
当然,您必须先订购然后过滤;例如,查询“女性演唱的前十首歌曲”以及“女性演唱的前十首歌曲”的查询可能非常不同!第一个是歌曲排序 - &gt;进入前十名 - &gt;应用过滤器。第二个是应用过滤器 - &gt;对歌曲进行排序 - &gt;进入前十名。
答案 1 :(得分:5)
Where()
的签名是这样的:
public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
因此,此方法将IEnumerable<int>
作为第一个参数。从IOrderedEnumerable<int>
返回的OrderBy
实现了IEnumerable<int>
,所以这没问题。
但正如您所看到的,Where
会返回IEnumerable<int>
而不是IOrderedEnumerable<int>
。这不能相互融合。
无论如何,该序列中的对象仍然具有相同顺序。所以你可以这样做
IEnumerable<int> tmp = new List<int>().OrderBy(x => x).Where(x => x > 1);
并获得您期望的序列。
但是当然你应该(出于性能原因)过滤你的对象首先,然后在需要排序的对象较少时对它们进行排序:
IOrderedEnumerable<int> tmp = new List<int>().Where(x => x > 1).OrderBy(x => x);
答案 2 :(得分:2)
tmp
变量的类型为IOrderedEnumerable
。
Where()
是一个函数,就像任何其他具有返回类型的函数一样,返回类型为IEnumerable
。 IEnumerable
和IOrderedEnumerable
不一样。
所以当你这样做时:
tmp = tmp.Where(x => x > 1);
您正尝试将Where()
函数调用的结果(IEnuemrable
)分配给tmp
变量IOrderedEnumerable
。它们不是直接兼容的,没有隐式转换,因此编译器会向您发送错误。
问题是你对tmp
变量的类型过于具体了。您可以进行一项简单的更改,通过使用tmp
变量稍微具体一点来完成所有工作:
IEnumerable<int> tmp = new List<int>().OrderBy(x => x);
tmp = tmp.Where(x => x > 1);
由于IOrderedEnumerable
继承自IEnumerable
,因此此代码全部有效。只要您以后不想再调用ThenBy()
,这应该会给出与您预期完全相同的结果,而不会在以后使用tmp
变量时失去任何其他功能。
如果真的需要IOrderedEnumerable
,您可以随时再次致电.OrderBy(x => x)
:
IOrderedEnumerable<int> tmp = new List<int>().OrderBy(x => x);
tmp = tmp.Where(x => x > 1).OrderBy(x => x);
再次,在大多数情况下(并非所有情况,但大多数情况下),您希望在开始排序之前完成过滤。换句话说,这更好:
var tmp = new List<int>().Where(x => x > 1).OrderBy(x => x);
答案 3 :(得分:1)
为什么没有实施?
最有可能的原因是LINQ设计者认为,与潜在的使用案例相比,实施,测试,文档等方面的努力是不够的。事实上,你是第一个听到抱怨的人。
但如果它对您如此重要,您可以自己添加缺少的功能(类似于@Jon Skeet MoreLINQ扩展库)。例如,像这样:
namespace MyLinq
{
public static class Extensions
{
public static IOrderedEnumerable<T> Where<T>(this IOrderedEnumerable<T> source, Func<T, bool> predicate)
{
return new WhereOrderedEnumerable<T>(source, predicate);
}
class WhereOrderedEnumerable<T> : IOrderedEnumerable<T>
{
readonly IOrderedEnumerable<T> source;
readonly Func<T, bool> predicate;
public WhereOrderedEnumerable(IOrderedEnumerable<T> source, Func<T, bool> predicate)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (predicate == null) throw new ArgumentNullException(nameof(predicate));
this.source = source;
this.predicate = predicate;
}
public IOrderedEnumerable<T> CreateOrderedEnumerable<TKey>(Func<T, TKey> keySelector, IComparer<TKey> comparer, bool descending) =>
new WhereOrderedEnumerable<T>(source.CreateOrderedEnumerable(keySelector, comparer, descending), predicate);
public IEnumerator<T> GetEnumerator() => Enumerable.Where(source, predicate).GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}
}
并付诸行动:
using System;
using System.Collections.Generic;
using System.Linq;
using MyLinq;
var test = Enumerable.Range(0, 100)
.Select(n => new { Foo = 1 + (n / 20), Bar = 1 + n })
.OrderByDescending(e => e.Foo)
.Where(e => (e.Bar % 2) == 0)
.ThenByDescending(e => e.Bar) // Note this compiles:)
.ToList();