【问题标题】:What's going on behind the scene of the 'foreach' loop? [duplicate]“foreach”循环的幕后发生了什么? [复制]
【发布时间】:2011-09-08 15:22:56
【问题描述】:

可能重复:
How do foreach loops work in C#?

我一直在搜索互联网,但无法找到任何关于 C# 中 foreach 循环的幕后真实情况的答案。

我知道这个问题与实际编码无关,但它困扰着我。我对 OO 编程,尤其是接口很陌生。我知道它们是合同,并且我了解 IEnumerableIEnumerator 的工作原理——至少我是这么认为的。

我一直在 MSDN 上阅读这篇文章: IEnumerable Interface

我了解一切是如何设置的。尽管在Main 循环中,我有点不清楚foreach 如何知道遍历_people 中的所有值。它怎么知道这个?它如何通过调用return new PeopleEnum(_people); 来跟踪Current

编辑:我看不出这是一个完全相同的副本。是的,它的相似理由和相同的问题被问到,但我们正在寻找不同的答案我想要的答案在上一个问题中没有讨论。

像 foreach(int i in obj) {...} 这样的 foreach 循环有点等同于

...“有点”不是我正在寻找的答案。

【问题讨论】:

  • 查看使用 Ildasm.exe 的 dll 代码,以获得应该有所帮助的简单 Foreach。
  • 我一遍又一遍地阅读。我知道它告诉你如何使用 foreach 循环,但它并没有真正解释编译器如何使用它。我对幕后更感兴趣。
  • @Adam:在可能重复的接受答案中,它大致显示了它归结为的代码。当然,下面 Eric 的回答要详细得多。
  • 好吧,现在当有人像我一样决定在 Google 上搜索“foreach 循环的幕后”时,它将位于顶部链接中,他们会得到一个非常具体、非常详细的答案。
  • 我不相信它是完全重复的。另一个问题是“哪些类型的类可以使用 foreach 循环”而不是详细解释它是如何工作的。

标签: c# foreach clr


【解决方案1】:

我鼓励您阅读 C# 规范的第 8.8.4 节,其中详细回答了您的问题。为方便起见,请在此处引用:


形式的foreach语句

foreach (V v in x) embedded-statement

然后扩展为:

{
    E e = ((C)(x)).GetEnumerator();
    try 
    {
        V v;
        while (e.MoveNext()) 
        {
            v = (V)(T)e.Current;
            embedded-statement
        }
    }
    finally 
    {
         code to Dispose e if necessary
    }
}

类型E、C、V、T是语义分析器推导出的枚举数、集合、循环变量和集合元素类型;有关详细信息,请参阅规范。

所以你去。 “foreach”只是编写“while”循环的一种更方便的方法,该循环调用 MoveNext 直到 MoveNext 返回 false。

一些微妙的事情:

  • 这不一定是生成的代码;所需要的只是我们生成产生相同结果的代码。例如,如果你“foreach”一个数组或一个字符串,我们只生成一个“for”循环(或循环,在多维数组的情况下),它索引数组或字符串的字符,而不是取以分配枚举器为代价。

  • 如果枚举器是值类型,那么处理代码可能会也可能不会选择在处理之前将枚举器装箱。不要依赖这种方式或其他方式。 (有关相关问题,请参阅 http://blogs.msdn.com/b/ericlippert/archive/2011/03/14/to-box-or-not-to-box-that-is-the-question.aspx。)

  • 同样,如果上面自动插入的类型转换被确定为恒等转换,则可能会忽略类型转换,即使这样做通常会导致值类型被复制。

  • 未来的C#版本很可能会将循环变量v的声明放在while循环体中;这将防止在 Stack Overflow 上每天报告一次的常见“修改关闭”错误。 [更新:This change has indeed been implemented in C# 5.]

【讨论】:

  • +1:我的扩展解释就这么多了...... :)
  • @reggie:请理解我不想知道如何实现foreach 循环或者我需要什么才能使用它。我想知道编译器是怎么想的。我想知道它看到了什么。我什至不知道这些规范文件在那里。我肯定会用它们来回答几个问题。谢谢。
  • @Adam:我鼓励您首先下载 C# 4 规范,然后考虑购买 Addison-Wesley 发布的印刷版。我和我的同事已经对它进行了广泛的注释,其中包含您可能感兴趣的各种详细解释。请参阅blogs.msdn.com/b/ericlippert/archive/2010/11/15/…
  • @Tigran:当然,如果我们确定不需要处理,那么整个 try-finally 都会被删除;我们不会最终生成一个空的 try-protected 区域!
  • @Gabe:通常没有什么不同。这主要是出于历史原因。考虑一下:如果集合是一个 T 数组怎么办?在 C# 1.0 中,数组实现了 IEnumerable,而不是 IEnumerable<T>,因此“Current”返回对象,而不是 T。
【解决方案2】:

这样考虑:

 var enumerator = <YourEnumerableHere>.GetEnumerator();

 while(enumerator.MoveNext())
    <YourForEachMethodBodyHere>

因此,当它调用GetEnumerator 时,您会将结果返回给PeopleEnum。每次调用MoveNext 方法时,PeopleEnum 的位置都会增加一,遍历列表。

当位置到达末尾时,MoveNext 调用返回 false,循环结束。

【讨论】:

    【解决方案3】:

    它知道遍历IEnumerable,因为这是语言设计者决定foreach 应该做的事情。 foreach 为您提供了一种自己循环遍历 IEnumerable 的简写方式。

    IEnumerator 类定义了“获取下一个元素”的含义。如果您愿意,您可以让 IEnumerator 返回每​​隔一个元素,但大多数人只想按顺序遍历每个元素。

    我不确定我是否已经回答了您的问题。如果没有,请告诉我,我会回复。

    【讨论】:

      猜你喜欢
      • 2019-06-05
      • 2015-07-25
      • 1970-01-01
      • 2011-10-19
      • 1970-01-01
      • 2011-07-12
      • 1970-01-01
      • 2018-03-29
      • 2019-10-17
      相关资源
      最近更新 更多