c# - 一圈还是两圈? (如何阅读 IL)

标签 c# linq loops foreach il

下面的 C# 是一个非常简单的循环,但我认为它是两个循环。我的一个同事说他认为这是一个单循环。你能告诉我它是一个循环还是两个循环?你也能告诉我如何阅读 IL 并向我的同事证明它是两个循环吗?

var ints = new List<int> {1, 2, 3, 4};

foreach (var i in ints.Where(x => x != 2))
{
    Console.WriteLine(i);
}

如果事实证明这实际上是一个很酷的循环。我仍然想知道如何读取 IL 并看到它只是一个循环。

.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

最佳答案

编译器将您的代码转换为 try-finally block ,首先它调用 GetEnumerator源上的方法,(这是从 Where) 返回的迭代器,然后进入 try block 。

第一条指令:

IL_0055:  br.s       IL_0067

跳转到 IL_0067调用MoveNext在迭代器上,然后加载 MoveNext 的结果进入局部变量(正如奇怪的名字所暗示的那样(CS $ 4 $ 0001)这是一个编译器生成的变量):

IL_006d:  stloc.s    CS$4$0001
IL_006f:  ldloc.s    CS$4$0001

此指令检查结果是否从 MoveNext 返回是true如果它跳回到IL_0057

IL_0071:  brtrue.s   IL_0057

然后继续执行,同样的操作一直运行到MoveNext返回 false .所以是的,代码中有一个循环。

您可以找到有关 IL 的更多信息documentation 中的说明.


除此之外try之前的代码块可能看起来令人困惑,但它基本上创建了一个 Func<int, bool> delegate 这是你的 lambda 表达式 ( x => x != 2 ) 然后将它传递给 Where方法。并将它的结果加载到 3.(实际上是第四个,3 是索引)这一行的局部变量:

IL_0054:  stloc.3

这是一个IEnumerator<int>正如您在参数列表中看到的那样。然后您的循环使用该迭代器。

关于c# - 一圈还是两圈? (如何阅读 IL),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27180212/

相关文章:

c# - 如何对 .FindAll() 的结果执行操作

vba - 指定循环遍历 Excel/VBA 的附加目录

c# - SerialPort.Write(byte[], int, int) 的文档是否具有误导性?

c# - 在 MVC Asp.net 中呈现局部 View 后是否可以启动 javascript 函数?

c# - dataAdapter .Fill 和 .Update 的比较

c# - 从列表中选择最后一个错误的项目

c# - NEST如何在子文档上查找字段

c# - 使用 C# 从相同类型的多个不同列表创建一个列表

java - 如何在 Java 中创建一个没有重复项的简单随机数生成器,并将结果存储在数组中以打印给最终用户?

java - java中如何退出内部循环并返回到主循环?