ericosg
ericosg

Reputation: 4965

LINQ Skip still enumerates skipped items

In the following test:

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

The result is:

1
2
3
4
5
6
7

4
5
6
7

Essentially, I observed that in this example, Skip() and Take() work well, Skip() is not as lazy as Take(). It seems that Skip() still enumerates the items skipped, even though it does not return them.

The same applies if I do Take() first. My best guess is that it needs to enumerate at least the first skip or take, in order to see where to go with the next one.

Why does it do this?

Upvotes: 22

Views: 3428

Answers (6)

Cory Nelson
Cory Nelson

Reputation: 30021

Skip() and Take() both operate on IEnumerable<>.

IEnumerable<> does not support skipping ahead -- it can only give you one item at a time. With this in mind, you can think of the Skip() more as a filter -- it still touches all the items in the source sequence, but it filters out however many you tell it to. And importantly, it filters them out from getting to whatever is next, not for whatever is in front of it.

So, by doing this:

data.Select(boom).Skip(3)

You are performing boom() on each item before they get to the Skip() filter.

If you instead changed it to this, it would filter prior to the Select and you would call boom() on only the remaining items:

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

Upvotes: 22

T_D
T_D

Reputation: 1728

This behaviour maybe will be changed in the future, as can be seen here in this discussion about a Pull Request that optimizes the behaviour of the Skip method for sources that implement the IList interface.

Upvotes: 1

user2023861
user2023861

Reputation: 8208

Not an answer, but think about how Skip(...) would be implemented. Here's one way to do it:

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;
    }
}

Notice that the whole list argument is enumerated even though only a subset is returned.

Upvotes: 0

Yeldar Kurmangaliyev
Yeldar Kurmangaliyev

Reputation: 34244

If you decompile Enumerable, you will see the following implementation of Skip:

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

and the following implementation of Take:

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

So, both of these LINQ methods actually enumerate through these items. The difference is whether an enumerated item will be placed in the resulting collection or not. That's how IEnumerable does work.

Upvotes: 5

Shaharyar
Shaharyar

Reputation: 12459

They both iterate the collection. And then turnout with the final collection. Its just like a simple for loop having a if else condition in it.

Moreover you're selecting it first using boom, it prints the item of collection.

By this example you can not tell if skip() or take() iterates the whole collection or not but the matter of fact, they do.

Upvotes: 2

assuming i have understood your question correctly.

the boom func executes before the skip operation.

try to select data after skip and take , you will get the exact.

Upvotes: 1

Related Questions