LINQ Skip仍然枚举跳过的项目

时间:2015-12-03 04:59:30

标签: c# .net linq

在以下测试中:

int[] data = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
Func<int, int> boom = x => { Console.WriteLine(x); return x; };
var res = data.Select(boom).Skip(3).Take(4).ToList();
Console.WriteLine();
res.Select(boom).ToList();

结果是:

1
2
3
4
5
6
7

4
5
6
7

基本上,我发现在这个示例中,Skip()Take()效果很好,Skip()并不像Take()那样懒惰。似乎Skip()仍然枚举跳过的项目,即使它没有返回它们。

如果我先Take(),这同样适用。我最好的猜测是,它需要至少枚举第一个跳过或取出,以便查看下一个跳转到哪里。

为什么会这样做?

6 个答案:

答案 0 :(得分:22)

Skip()Take()都在IEnumerable<>上运行。

IEnumerable<>不支持向前跳过 - 它一次只能为您提供一个项目。考虑到这一点,您可以将Skip()更多地视为过滤器 - 它仍会触及源序列中的所有项目,但它会过滤掉您告诉它的许多项目。而且重要的是,它会将它们从过去的任何东西中过滤掉,而不是用于它前面的任何东西。

所以,通过这样做:

data.Select(boom).Skip(3)

您在之前对每个项目执行boom()进入Skip()过滤器。

如果您改为将其更改为此,则会在Select之前进行过滤,您只会在其余项目上调用boom()

data.Skip(3).Take(4).Select(boom)

答案 1 :(得分:5)

如果您反编译Enumerable,您会看到Skip的以下实现:

while (count > 0 && e.MoveNext())
  --count;

以及Take的以下实现:

foreach (TSource source1 in source)
{
  yield return source1;
  if (--count == 0) break;
}

因此,这两种LINQ方法实际上都是通过这些项进行枚举的。不同之处在于枚举项是否将放置在生成的集合中。这就是IEnumerable的工作方式。

答案 2 :(得分:2)

他们都迭代了这个集合。然后结束最后的收集。它就像一个简单的for loop,其中包含if else条件。

此外,您首先使用boom选择它,它会打印收集项目。

通过这个例子,您无法判断skip()take()是否迭代整个集合,但事实上,他们确实如此。

答案 3 :(得分:1)

假设我已正确理解你的问题。

动作func在跳过操作之前执行。

尝试在跳过后选择数据,你会得到确切的数据。

答案 4 :(得分:1)

这种行为可能会在未来发生变化,可以在本次讨论中看到here有关Pull Request的优化实现IList接口的源的Skip方法的行为。

答案 5 :(得分:0)

不是答案,而是考虑如何实施Skip(...)。这是一种方法:

public static IEnumerable<T> Skip<T>(this IEnumerable<T> list, int count)
{
    foreach (var item in list)
    {
        if (count > 0) count--;
        else yield return item;
    }
}

请注意,即使只返回了一个子集,也会枚举整个list参数。