47

I want to create a foreach which skips the first item. I've seen elsewhere that the easiest way to do this is to use myCollection.Skip(1), but I have a question:

The MSDN documentation on .Skip() describes that it "Bypasses a specified number of elements in a sequence and then returns the remaining elements." Does this mean that a call to

foreach(object i in myCollection.Skip(1))
{ ... }

Would the program have to perform .Skip(1) every time the foreach iterates? Or does foreach (somewhat like a switch) not require multiple evaluations of the array?

Would it be more efficient to create a dummy var _dummy = myCollection.Skip(1) and iterate on this instead?

Wasabi
  • 2,879
  • 3
  • 26
  • 48
  • 3
    `Skip` returns an Enumerable. This is what is used to generate an enumerator. The enumerator is used by foreach in order to fetch a type object from each enumerable in the enumerator. – Travis J Nov 08 '13 at 19:39
  • I may be way off base here, but it seems like a simple solution (rather than using `Skip`) would be to use a standard `for` loop and have the index start at `1`? – MirroredFate Nov 08 '13 at 23:03
  • 1
    @MirroredFate yeah, that would work. However, part of the point of `foreach` is its improved readability, which is what I was hoping for. – Wasabi Nov 09 '13 at 02:09

5 Answers5

53

I just mocked your code with this

foreach(var v in Enumerable.Range(1,10).Skip(1))
    v.Dump();

And here is the IL generated.

IL_0001:  nop         
IL_0002:  ldc.i4.1    
IL_0003:  ldc.i4.s    0A 
IL_0005:  call        System.Linq.Enumerable.Range
IL_000A:  ldc.i4.1    
IL_000B:  call        System.Linq.Enumerable.Skip//Call to Skip
IL_0010:  callvirt    System.Collections.Generic.IEnumerable<System.Int32>.GetEnumerator
IL_0015:  stloc.1     // CS$5$0000
IL_0016:  br.s        IL_0026
IL_0018:  ldloc.1     // CS$5$0000
IL_0019:  callvirt    System.Collections.Generic.IEnumerator<System.Int32>.get_Current
IL_001E:  stloc.0     // v
IL_001F:  ldloc.0     // v
IL_0020:  call        LINQPad.Extensions.Dump
IL_0025:  pop         
IL_0026:  ldloc.1     // CS$5$0000
IL_0027:  callvirt    System.Collections.IEnumerator.MoveNext
IL_002C:  stloc.2     // CS$4$0001
IL_002D:  ldloc.2     // CS$4$0001
IL_002E:  brtrue.s    IL_0018
IL_0030:  leave.s     IL_0042
IL_0032:  ldloc.1     // CS$5$0000
IL_0033:  ldnull      
IL_0034:  ceq         
IL_0036:  stloc.2     // CS$4$0001
IL_0037:  ldloc.2     // CS$4$0001
IL_0038:  brtrue.s    IL_0041
IL_003A:  ldloc.1     // CS$5$0000
IL_003B:  callvirt    System.IDisposable.Dispose
IL_0040:  nop         
IL_0041:  endfinally  

As you can see Skip is called only once.

Equivalent c# code would look something like this

IEnumerator<int> e = ((IEnumerable<int>)values).GetEnumerator();//Get the enumerator
try
{
  int m;//This variable is here prior to c#5.0
  while(e.MoveNext())
  {//int m; is declared here starting from c#5.0
    m = (int)(int)e.Current;
    //Your code here
  }
}
finally
{
  if (e != null) ((IDisposable)e).Dispose();
}

Consider the below code, If foreach calls VeryLongRunningMethodThatReturnsEnumerable at each iteration then that would be nightmare. Huge flaw in the design of the language. Fortunately it doesn't do that.

foreach(var obj in VeryLongRunningMethodThatReturnsEnumerable())
{
   //Do something with that obj
}
Sriram Sakthivel
  • 72,067
  • 7
  • 111
  • 189
  • Thanks, that clears it up. Its just that when doing step-by-step debugging I can see the `myCollection.Skip(1)` being "highlighted" for evaluation. I just wasn't sure if that was in order to evaluate `myCollection.Skip(1).MoveNext()` or just `_dummy.MoveNext()`, where _dummy is the anonymous collection secretly created prior to calling the foreach loop. Thanks for clearing that up. And at seeing your edit, that was precisely what I was afraid of. – Wasabi Nov 08 '13 at 19:41
  • 1
    @ps06756 This is with the help of [LinqPad](http://www.linqpad.net/) There are number of decompilers available, Reflector, IL-Spy, DotPeek, JustDecompile etc – Sriram Sakthivel Jan 20 '14 at 19:56
37

You should understand the way foreach works. This foreach loop:

foreach(T t in GetSomeEnumerable())
    DoSomethingWithT(t);

is equivalent to this code:

var e = GetSomeEnumerable().GetEnumerator();
try{
    while(e.MoveNext()){
        T t = (T)e.Current; // unless e is the generic IEnumerator<T>,
                            // in which case, there is no cast
        DoSomethingWithT(t);
    }
}finally{
    if(e is IDisposable)
        e.Dispose();
}
P Daddy
  • 28,912
  • 9
  • 68
  • 92
  • 2
    I was writing just about the same darn answer. FWIW, the 5.0 spec lays this out on pg 247. This answer is easier to follow than IL IMO. – P.Brian.Mackey Nov 08 '13 at 20:50
  • 1
    Agreed.. The accepted answer with IL makes it look "accurate" I guess.. but IMHO, it is out of context, and it's more important to understand how foreach works.. than look at IL for the Skip call. – Vikas Gupta Dec 06 '14 at 09:36
7

Pull it out and it probably becomes clearer.

var myCollection = new List<object>();
var skipped = myCollection.Skip(1);

foreach (var i in skipped) {
    Console.WriteLine(i.ToString());
}

So skipped is just an IEnumerable that foreach is enumerates now.

Here's what the IL looks like in that case:

IL_0000:  newobj      System.Collections.Generic.List<System.Object>..ctor
IL_0005:  stloc.0     // myCollection
IL_0006:  ldloc.0     // myCollection
IL_0007:  ldc.i4.1    
IL_0008:  call        System.Linq.Enumerable.Skip
IL_000D:  stloc.1     // skipped
IL_000E:  ldloc.1     // skipped
IL_000F:  callvirt    System.Collections.Generic.IEnumerable<System.Object>.GetEnumerator
IL_0014:  stloc.3     // CS$5$0000
IL_0015:  br.s        IL_0029
IL_0017:  ldloc.3     // CS$5$0000
IL_0018:  callvirt    System.Collections.Generic.IEnumerator<System.Object>.get_Current
IL_001D:  stloc.2     // i
IL_001E:  ldloc.2     // i
IL_001F:  callvirt    System.Object.ToString
IL_0024:  call        System.Console.WriteLine
IL_0029:  ldloc.3     // CS$5$0000
IL_002A:  callvirt    System.Collections.IEnumerator.MoveNext
IL_002F:  brtrue.s    IL_0017
IL_0031:  leave.s     IL_003D
IL_0033:  ldloc.3     // CS$5$0000
IL_0034:  brfalse.s   IL_003C
IL_0036:  ldloc.3     // CS$5$0000
IL_0037:  callvirt    System.IDisposable.Dispose
IL_003C:  endfinally  

The IL for your code looks similar:

var myCollection = new List<object>();

foreach (var i in myCollection.Skip(1)) {
    Console.WriteLine(i.ToString());
}

IL_0000:  newobj      System.Collections.Generic.List<System.Object>..ctor
IL_0005:  stloc.0     // myCollection
IL_0006:  ldloc.0     // myCollection
IL_0007:  ldc.i4.1    
IL_0008:  call        System.Linq.Enumerable.Skip <-- 1 Call to .Skip() outside the loop.
IL_000D:  callvirt    System.Collections.Generic.IEnumerable<System.Object>.GetEnumerator
IL_0012:  stloc.2     // CS$5$0000
IL_0013:  br.s        IL_0027
IL_0015:  ldloc.2     // CS$5$0000
IL_0016:  callvirt    System.Collections.Generic.IEnumerator<System.Object>.get_Current
IL_001B:  stloc.1     // i
IL_001C:  ldloc.1     // i
IL_001D:  callvirt    System.Object.ToString
IL_0022:  call        System.Console.WriteLine
IL_0027:  ldloc.2     // CS$5$0000
IL_0028:  callvirt    System.Collections.IEnumerator.MoveNext
IL_002D:  brtrue.s    IL_0015
IL_002F:  leave.s     IL_003B
IL_0031:  ldloc.2     // CS$5$0000
IL_0032:  brfalse.s   IL_003A
IL_0034:  ldloc.2     // CS$5$0000
IL_0035:  callvirt    System.IDisposable.Dispose
IL_003A:  endfinally  

It still has just the one .Skip() call.

Restore the Data Dumps
  • 38,967
  • 12
  • 96
  • 122
  • I'm not sure this answers the question, as it doesn't address what happens if the call to Skip() happens inside the foreach declaration. Or does it, and I'm being dense? – NWard Nov 08 '13 at 19:31
  • This code does not match the original question. You need to use skipped in the foreach instead of myCollection – Mr.Mindor Nov 09 '13 at 00:10
  • @Mr.Mindor: Read the second example. – Restore the Data Dumps Nov 09 '13 at 00:12
  • Not sure your point. In the first example, "Pull it out and it probably becomes clearer." Generally when I see "pull it out" in an explanation, the expectation is that the code is equivalent. That isn't the case with your first example, It iterates over the whole collection. – Mr.Mindor Nov 09 '13 at 00:27
  • @Mr.Mindor: Ah yes. I had a typo. Thank you. – Restore the Data Dumps Nov 09 '13 at 01:35
  • I gave this answer from Jason a +1 because he took the time to show the IL for both the method call in-line and the method call "pulled out". However, the concise VeryLongRunningMethodThatReturnsEnumerable() example from the accepted response hammers home the answer quite effectively, so that also gets my +1 as being helpful for my purposes today. – TonyG Nov 25 '15 at 23:11
4

The whole expression with Skip will get called only once. Skip uses a deferred execution so that it is executed once there are actions that does not use deferred execution. In that moment an expression tree is builded on the background and reference to an instance of IEnumerable is yielded back to the caller who uses it if nothing changes.

Ondrej Janacek
  • 12,486
  • 14
  • 59
  • 93
2

What's your iterating on is the result of the command:

myCollection.Skip(1)

This effectively returns an IEnumerable of the type of myCollection which has omitted the first element. So your foreach then is against the new IEnumerable which lacks the first element. The foreach forces the actual evaluation of the yielded Skip(int) method via enumeration (its execution is deferred until enumeration, just like other LINQ methods such as Where, etc.) It would be the same as:

var mySkippedCollection = myCollection.Skip(1);
foreach (object i in mySkippedCollection)
...

Here's the code that Skip(int) actually ends up performing:

private static IEnumerable<TSource> SkipIterator<TSource>(IEnumerable<TSource> source, int count)
{
    using (IEnumerator<TSource> enumerator = source.GetEnumerator())
    {
        while (count > 0 && enumerator.MoveNext())
        {
            count--;
        }
        if (count <= 0)
        {
            while (enumerator.MoveNext())
            {
                yield return enumerator.Current; // <-- here's your lazy eval
            }
        }
    }
    yield break;
}
Haney
  • 32,775
  • 8
  • 59
  • 68