在以下测试中:
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()
,这同样适用。我最好的猜测是,它需要至少枚举第一个跳过或取出,以便查看下一个跳转到哪里。
为什么会这样做?
答案 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
参数。