Visual Studio C# 编译器在 Foreach 循环中做什么

本文关键字:循环 什么 Foreach Studio 编译器 Visual | 更新日期: 2023-09-27 17:56:39

单步执行 C# 代码时,我注意到调试器在 foreach 声明中停止三次:

  • 首先,它突出了收藏。
  • 然后,它突出显示 in 运算符。
  • 最后,它突出显示变量。

我想了解调试器在做什么。例如,它在for循环中是有意义的。在初始化期间,它会分配变量,然后检查条件。在每个后续循环开始时,它会更新变量,然后检查条件。

似乎在foreach中,它需要在初始化期间在in运算符处获取一个枚举器,然后仅在每个循环开始时分配下一项。

我正在使用Visual Studio。

Visual Studio C# 编译器在 Foreach 循环中做什么

考虑这个例子:

IEnumerable<int> numbers = Enumerable.Range(1, 10);
// foreach
foreach (int n in numbers)
{
    Console.WriteLine(n);
}

这可以翻译成:

IEnumerable<int> numbers = Enumerable.Range(1, 10);
// foreach
IEnumerator<int> e = numbers.GetEnumerator();
while (e.MoveNext())
{
    int n = e.Current;
    Console.WriteLine(n);
}

因此,看一下调试步骤:

  1. 调试器首先突出显示 numbers 关键字,此处调用 GetEnumerator 方法。
  2. 然后它将突出显示in关键字,这意味着调用e.MoveNext()
  3. 之后,它将突出显示n变量,这意味着调用e.Current
  4. 最后,它将执行循环的主体,在我们的例子中,这是Console.WriteLine(n)
重复步骤 2

34,直到步骤 2 返回 false,或者枚举器完全迭代。

这与编译器无关,而与运行时有关。 也就是说,您可以查看 IL 以查看生成了哪些步骤。 下面是一个快速而脏的 LinqPad 生成的示例:

法典:

void Main()
{
    var x = new List<string>();
    foreach (var y in x) { }
}

IL:

IL_0000:  nop         
IL_0001:  newobj      System.Collections.Generic.List<System.String>..ctor
IL_0006:  stloc.0     // x
IL_0007:  nop         
IL_0008:  ldloc.0     // x
IL_0009:  callvirt    System.Collections.Generic.List<System.String>.GetEnumerator
IL_000E:  stloc.1     
IL_000F:  br.s        IL_001B
IL_0011:  ldloca.s    01 
IL_0013:  call        System.Collections.Generic.List<System.String>+Enumerator.get_Current
IL_0018:  stloc.2     // y
IL_0019:  nop         
IL_001A:  nop         
IL_001B:  ldloca.s    01 
IL_001D:  call        System.Collections.Generic.List<System.String>+Enumerator.MoveNext
IL_0022:  brtrue.s    IL_0011
IL_0024:  leave.s     IL_0035
IL_0026:  ldloca.s    01 
IL_0028:  constrained. System.Collections.Generic.List<>.Enumerator
IL_002E:  callvirt    System.IDisposable.Dispose
IL_0033:  nop         
IL_0034:  endfinally  
IL_0035:  ret   

说明IL_0009、IL_0013和IL_001D是最有趣的。他们获取枚举器,找到当前项,然后将位置移动到下一个位置。

井集合继承了称为 IEnumerable 的东西(转到List类型的定义)。IEnumerable 有一个名为 GetNext() 的方法。这将获取集合中的下一个对象。当命中下一个循环时,执行 GetNext() 方法,允许将var foo in bar分配给 GetNext() 给出的对象。这就是为什么它被突出显示在第二位。