LINQ Skip仍然枚举跳过的项

  • 本文关键字:枚举 Skip LINQ 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(),同样适用。我的最佳猜测是,它至少需要列举第一次跳过或采取,以便查看下一次该去哪里。

它为什么要这样做?

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

IEnumerable<>不支持提前跳过--它一次只能给您一个项目。考虑到这一点,你可以把Skip()更多地看作一个过滤器——它仍然会接触到源序列中的所有项目,但它会过滤掉你告诉它的数量。重要的是,它会过滤它们,使它们不进入下一个项目,而不是前面的项目。

因此,通过这样做:

data.Select(boom).Skip(3)

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

如果您改为这样,它将在Select之前进行筛选,并且您将仅对剩余项目调用boom()

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

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

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

以及CCD_ 17的以下实现:

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

因此,这两个LINQ方法实际上都枚举了这些项。不同之处在于枚举项是否会被放置在结果集合中。IEnumerable就是这样工作的。

它们都迭代集合。然后进行最后的收集。它就像一个简单的for loop,里面有一个if else条件

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

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

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

起重臂func在跳过操作之前执行。

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

这种行为将来可能会改变,正如在本次关于Pull Request的讨论中所看到的,该讨论优化了实现IList接口的源的Skip方法的行为。

不是答案,但想想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参数。

最新更新