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

C# 一圈还是两圈?(如何阅读IL),c#,linq,loops,foreach,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,并看到它只是一个循

下面的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块,首先调用源代码上的GetEnumerator方法,该方法是从那里返回的迭代器,然后进入try块

第一个指令:

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返回的结果是否为真,如果为真,则跳回IL_0057

然后继续执行,相同的操作将一直运行,直到MoveNext返回false

您可以在中找到有关IL指令的更多信息

除此之外,try之前的代码块可能看起来很混乱,但它基本上创建了一个Func委托,即lambda表达式x=>x!=2然后将其传递给Where方法,并将其结果加载到3中。实际上它是第四个,3是这行中的索引局部变量:

IL_0054:  stloc.3

它是一个IEnumerator,如您在参数列表中所看到的。然后您的循环使用该迭代器

编译器将代码转换为try-finally块,首先调用源代码上的GetEnumerator方法,该方法是从那里返回的迭代器,然后进入try块

第一个指令:

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返回的结果是否为真,如果为真,则跳回IL_0057

然后继续执行,相同的操作将一直运行,直到MoveNext返回false

您可以在中找到有关IL指令的更多信息

除此之外,try之前的代码块可能看起来很混乱,但它基本上创建了一个Func委托,即lambda表达式x=>x!=2然后将其传递给Where方法,并将其结果加载到3中。实际上它是第四个,3是这行中的索引局部变量:

IL_0054:  stloc.3
它是一个IEnumerator,如您在参数列表中所看到的。然后您的循环使用该迭代器

这是一个单循环。Where方法不会首先对所有项执行,它会在枚举项时过滤这些项

Where方法不生成枚举的集合,而是创建一个枚举器,在枚举项时对其进行条件测试。这些项目将按如下方式进行处理:

foreach (var i in ints) {
  if (i != 2) {
    Console.WriteLine(i);
  }
}
该代码包含用于创建列表的速记代码、使用枚举器循环的代码以及一系列其他内容,因此很难看到它与IL代码的关系。这大约是扩展速记代码时代码的样子:

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;
}
这是一个单圈。Where方法不会首先对所有项执行,它会在枚举项时过滤这些项

Where方法不生成枚举的集合,而是创建一个枚举器,在枚举项时对其进行条件测试。这些项目将按如下方式进行处理:

foreach (var i in ints) {
  if (i != 2) {
    Console.WriteLine(i);
  }
}
该代码包含用于创建列表的速记代码、使用枚举器循环的代码以及一系列其他内容,因此很难看到它与IL代码的关系。这大约是扩展速记代码时代码的样子:

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;
}

是什么让你认为这是两个循环?看IL和它有什么关系?我猜有两个循环,完整的LINQ将首先在所有元素上执行,然后将结果传递给foreach?如果有人知道会很酷,我不会被要求去查:P@starlight54不完全是。其中返回一个迭代器,该迭代器是具有当前属性和MoveNext方法的对象。它懒散地处理输入。在foreach体的每次执行中,调用其中的MoveNext,它将在内部进一步循环int,直到找到一个与条件匹配的,然后返回它。但是,ints列表仍然只迭代一次。它实际上都是同一个循环的一部分。只需测量一下,就可以很容易地看到^2上和On上的差异。LINQ是懒惰的。这是一个循环,你怎么会认为是两个循环?看IL和它有什么关系?我猜有两个循环,完整的LINQ将首先在所有元素上执行,然后将结果传递给foreach?如果有人知道会很酷,我不会被要求去查:P@starlight54不完全是。其中返回一个迭代器,whi
ch是具有当前属性和MoveNext方法的对象。它懒散地处理输入。在foreach体的每次执行中,调用其中的MoveNext,它将在内部进一步循环int,直到找到一个与条件匹配的,然后返回它。但是,ints列表仍然只迭代一次。它实际上都是同一个循环的一部分。只需测量一下,就可以很容易地看到^2上和On上的差异。LINQ是懒惰的。这是一个循环。在幕后的方法中执行另一个循环。这显然不是问题所想要的。也就是说,虽然LINQ确实是用迭代器方法实现的,迭代器方法中有循环,但这里没有任何嵌套循环。在运行时,它实际上是一个大循环,每个迭代器方法都在这里,只有一个是由下一个外部循环调用的状态机。@PeterDuniho是的,你是对的,但我不是说它是嵌套循环。我的意思是在幕后执行另一个循环。所以这个循环是基于隐藏循环的结果运行的。无论如何,我删除了那个部分,因为它似乎不适用于这个特定的情况。因为Where的重载只返回一个迭代器实例,所以没有另一个循环在进行…所以这个循环基于隐藏循环的结果运行。-这就是我的观点。此循环不会在隐藏循环的结果上运行。这两个循环作为一个较大的循环同时运行。这就是返回迭代器实例的原因;迭代器方法就是这样工作的。迭代器方法中的循环不是一次执行的……每次迭代仅在其他代码枚举迭代时根据需要进行,即在另一个循环中。在幕后的方法中执行另一个循环这显然不是问题想要的。也就是说,虽然LINQ确实是用迭代器方法实现的,迭代器方法中有循环,但这里没有任何嵌套循环。在运行时,它实际上是一个大循环,每个迭代器方法都在这里,只有一个是由下一个外部循环调用的状态机。@PeterDuniho是的,你是对的,但我不是说它是嵌套循环。我的意思是在幕后执行另一个循环。所以这个循环是基于隐藏循环的结果运行的。无论如何,我删除了那个部分,因为它似乎不适用于这个特定的情况。因为Where的重载只返回一个迭代器实例,所以没有另一个循环在进行…所以这个循环基于隐藏循环的结果运行。-这就是我的观点。此循环不会在隐藏循环的结果上运行。这两个循环作为一个较大的循环同时运行。这就是返回迭代器实例的原因;迭代器方法就是这样工作的。迭代器方法中的循环不是一次性执行的……每次迭代仅在其他代码枚举该迭代(即在另一个循环中)时根据需要进行。