【问题标题】:What is the Visual Studio C# Compiler Doing in a Foreach Loop [duplicate]Visual Studio C# 编译器在 Foreach 循环中做什么 [重复]
【发布时间】:2016-07-11 19:49:55
【问题描述】:

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

  • 首先,它突出显示集合。
  • 然后,它突出显示 in 运算符。
  • 最后突出显示变量。

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

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

我正在使用 Visual Studio。

【问题讨论】:

  • 编译器正在打盹,因为编译器是在编译时使用的,而不是运行时使用的(除非你在使用它们时会知道的特殊情况)。听起来您将编译器与调试器混淆了,您的问题现已更新以反映这一点。
  • 这个SO answer的一个很好的解释

标签: c# visual-studio foreach


【解决方案1】:

考虑这个例子:

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)

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

【讨论】:

    【解决方案2】:

    这与编译器无关,与运行时有关。也就是说,您可以查看 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 是最有趣的。他们获取枚举器,找到当前项目,然后将位置移动到下一个位置。

    【讨论】:

      【解决方案3】:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-05-08
        • 1970-01-01
        • 2023-03-26
        • 1970-01-01
        • 2011-08-29
        • 1970-01-01
        • 2014-06-07
        • 2010-12-18
        相关资源
        最近更新 更多