简体   繁体   中英

One loop or two? (how to read the IL)

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM