The C# below is a very simple loop but I think it is two loops. A coworker of mine says that he thinks it is a single loop. Can you tell me if it is one loop or two loops? Can you also tell me how to read the IL and prove to my coworker that it is two loops?
var ints = new List<int> {1, 2, 3, 4};
foreach (var i in ints.Where(x => x != 2))
{
Console.WriteLine(i);
}
If it turns out that this is actually one loop that is cool to. I would still like to know how I can read the IL and see that it is only one loop.
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 137 (0x89)
.maxstack 3
.locals init ([0] class [mscorlib]System.Collections.Generic.List`1<int32> ints,
[1] int32 i,
[2] class [mscorlib]System.Collections.Generic.List`1<int32> '<>g__initLocal0',
[3] class [mscorlib]System.Collections.Generic.IEnumerator`1<int32> CS$5$0000,
[4] bool CS$4$0001)
IL_0000: nop
IL_0001: newobj instance void class [mscorlib]System.Collections.Generic.List`1<int32>::.ctor()
IL_0006: stloc.2
IL_0007: ldloc.2
IL_0008: ldc.i4.1
IL_0009: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0)
IL_000e: nop
IL_000f: ldloc.2
IL_0010: ldc.i4.2
IL_0011: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0)
IL_0016: nop
IL_0017: ldloc.2
IL_0018: ldc.i4.3
IL_0019: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0)
IL_001e: nop
IL_001f: ldloc.2
IL_0020: ldc.i4.4
IL_0021: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0)
IL_0026: nop
IL_0027: ldloc.2
IL_0028: stloc.0
IL_0029: nop
IL_002a: ldloc.0
IL_002b: ldsfld class [mscorlib]System.Func`2<int32,bool> ConsoleApplication1.Program::'CS$<>9__CachedAnonymousMethodDelegate2'
IL_0030: brtrue.s IL_0045
IL_0032: ldnull
IL_0033: ldftn bool ConsoleApplication1.Program::'<Main>b__1'(int32)
IL_0039: newobj instance void class [mscorlib]System.Func`2<int32,bool>::.ctor(object,
native int)
IL_003e: stsfld class [mscorlib]System.Func`2<int32,bool> ConsoleApplication1.Program::'CS$<>9__CachedAnonymousMethodDelegate2'
IL_0043: br.s IL_0045
IL_0045: ldsfld class [mscorlib]System.Func`2<int32,bool> ConsoleApplication1.Program::'CS$<>9__CachedAnonymousMethodDelegate2'
IL_004a: call class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0> [System.Core]System.Linq.Enumerable::Where<int32>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>,
class [mscorlib]System.Func`2<!!0,bool>)
IL_004f: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> class [mscorlib]System.Collections.Generic.IEnumerable`1<int32>::GetEnumerator()
IL_0054: stloc.3
.try
{
IL_0055: br.s IL_0067
IL_0057: ldloc.3
IL_0058: callvirt instance !0 class [mscorlib]System.Collections.Generic.IEnumerator`1<int32>::get_Current()
IL_005d: stloc.1
IL_005e: nop
IL_005f: ldloc.1
IL_0060: call void [mscorlib]System.Console::WriteLine(int32)
IL_0065: nop
IL_0066: nop
IL_0067: ldloc.3
IL_0068: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
IL_006d: stloc.s CS$4$0001
IL_006f: ldloc.s CS$4$0001
IL_0071: brtrue.s IL_0057
IL_0073: leave.s IL_0087
} // end .try
finally
{
IL_0075: ldloc.3
IL_0076: ldnull
IL_0077: ceq
IL_0079: stloc.s CS$4$0001
IL_007b: ldloc.s CS$4$0001
IL_007d: brtrue.s IL_0086
IL_007f: ldloc.3
IL_0080: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_0085: nop
IL_0086: endfinally
} // end handler
IL_0087: nop
IL_0088: ret
} // end of method Program::Main
Compiler translates your code into try-finally block, first it calls GetEnumerator
method on the source, (which is the iterator returned from Where)
then enters the try block.
The first insruction:
IL_0055: br.s IL_0067
Jumps to the IL_0067
to call MoveNext
on the iterator and then loads the result of MoveNext
into the local variable (as the weird name suggests (CS$4$0001) this is a compiler generated variable):
IL_006d: stloc.s CS$4$0001
IL_006f: ldloc.s CS$4$0001
This instruction checks if the result return from MoveNext
is true
and if it is jumps back to the IL_0057
IL_0071: brtrue.s IL_0057
Then the execution continues, the same operation is keep running until MoveNext
returns false
.So yes, there is one loop in the code.
You can find more information about the IL
instructions in the documentation .
In addition to this the code before try
block might seem confusing but it basically creates a Func<int, bool>
delegate which is your lambda expression ( x => x != 2
) then passes it to Where
method.And loads the result of it into the 3. (actually it's fourth, 3
is the index) local variable in this line:
IL_0054: stloc.3
Which is an IEnumerator<int>
as you can see in the parameter list.Then your loop uses that iterator.
It's a single loop. The Where
method won't execute first on all item, it will filter the items as you enumerate them.
The Where
method doesn't produce a collection that you enumerate, it creates an enumerator that will test the condition on the items as you enumerate them. The items will be processed in the same was as:
foreach (var i in ints) {
if (i != 2) {
Console.WriteLine(i);
}
}
The code contains shorthand code for creating a list, looping using an enumerator, and a bunch of other stuff, so it can be hard to see how it relates to the IL code. This is approximately what the code would look like when the shorthand code is expanded:
Func<int, bool> cachedDelegate;
void Main(string[] args) {
List<int> temp;
int i;
List<int> ints;
IEnumerator<int> enumerator;
temp = new List<int>();
temp.Add(1);
temp.Add(2);
temp.Add(3);
temp.Add(4);
ints = temp;
if (cachedDelegate == null) {
cachedDelegate = new Func<int, bool>(Check);
}
enumerator = ints.Where(cachedDelegate).GetEnumerator();
try {
while (enumerator.MoveNext()) {
i = enumerator.Current;
Console.WriteLine(i);
}
} finally {
if (enumerator != null) {
enumerator.Dispose();
}
}
}
bool Check(int x) {
return x != 2;
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.