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 h
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
}
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.
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();
}
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.
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;
}