Visual Studio C# 编译器在 Foreach 循环中做什么
本文关键字:循环 什么 Foreach Studio 编译器 Visual | 更新日期: 2023-09-27 17:56:39
单步执行 C# 代码时,我注意到调试器在 foreach
声明中停止三次:
- 首先,它突出了收藏。
- 然后,它突出显示 in 运算符。
- 最后,它突出显示变量。
我想了解调试器在做什么。例如,它在for
循环中是有意义的。在初始化期间,它会分配变量,然后检查条件。在每个后续循环开始时,它会更新变量,然后检查条件。
似乎在foreach
中,它需要在初始化期间在in运算符处获取一个枚举器,然后仅在每个循环开始时分配下一项。
我正在使用Visual Studio。
考虑这个例子:
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);
}
因此,看一下调试步骤:
- 调试器首先突出显示
numbers
关键字,此处调用GetEnumerator
方法。 - 然后它将突出显示
in
关键字,这意味着调用e.MoveNext()
- 之后,它将突出显示
n
变量,这意味着调用e.Current
- 最后,它将执行循环的主体,在我们的例子中,这是
Console.WriteLine(n)
、3 和 4,直到步骤 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()
给出的对象。这就是为什么它被突出显示在第二位。