在过滤

时间:2016-06-22 16:06:07

标签: c# linq

我已经简化了问题。我有一个有序的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,但是在应用了额外的地方后,我将不得不重新排序,即使它将处于其原始的有序状态

4 个答案:

答案 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()是一个函数,就像任何其他具有返回类型的函数一样,返回类型为IEnumerableIEnumerableIOrderedEnumerable不一样。

所以当你这样做时:

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