当循环函数结果时,foreach如何工作?

38

假设我有以下代码:

foreach(string str in someObj.GetMyStrings())
{
    // do some stuff
}

在循环的每次迭代中,someObj.GetMyStrings()会被调用吗?与此相比,进行以下操作会更好吗:

List<string> myStrings = someObj.GetMyStrings();
foreach(string str in myStrings)
{
    // do some stuff
}

的翻译是:

?


23
试一试吧 - 在 GetMyStrings() 中设置一个断点,看看调试器会在那里停止多少次! - AakashM
4个回答

54

该函数仅被调用一次,以返回一个IEnumerator<T>;之后,MoveNext()方法和Current属性被用来迭代结果:

foreach (Foo f in GetFoos())
{
    // Do stuff
}

有些等价于:

using (IEnumerator<Foo> iterator = GetFoos().GetEnumerator())
{
    while (iterator.MoveNext())
    {
        Foo f = iterator.Current;
        // Do stuff
    }
}
请注意,迭代器在结束时被处理 - 这对于从迭代器块处处理资源的释放特别重要,例如:
public IEnumerable<string> GetLines(string file)
{
    using (TextReader reader = File.OpenText(file))
    {
        string line;
        while ((line = reader.ReadLine()) != null)
        {
            yield return line;
        }
    }
}
在上面的代码中,你真的希望在迭代完成时关闭文件,编译器巧妙地实现了IDisposable来实现这一点。

请纠正我如果我错了,但我认为C#编译器不允许您在foreach循环运行时编辑IEnumerable。 - Shaswat Rungta
如果你尝试在 foreach 循环开始执行后编辑 IEnumerable,应用程序会在运行时抛出异常。请尝试运行此代码 http://pastebin.com/MztPyiuA。 - Shaswat Rungta
编译器无法抛出异常 - 它们不参与执行时间。而且您还没有说“编辑IEnumerable”是什么意思。我怀疑您正在混淆某些集合类型(例如List<T>)中设置的保护措施。如果在迭代List<T>时修改它,是的,您会收到异常。这是由List<T>实现做出的决定 - 我不明白它与这个问题有什么关系。 - Jon Skeet
@ShaswatRungta:“循环”根本无法检测到这一点-在IEnumerableIEnumerator上没有任何内容可以监视更改。这是每个集合的决定。例如,并发集合*允许在迭代时进行修改。 - Jon Skeet
让我们在聊天中继续这个讨论 - Shaswat Rungta
显示剩余2条评论

19

不会的,该函数将被调用一次以获取IEnumerable,然后将重复调用MoveNext和Current。


4

GetMyStrings() 返回一个类型为 IEnumerable 的对象。运行时知道如何处理它。它调用 IEnumerable.GetEnumerator(),然后在该枚举器对象上调用 MoveNext() 和 Current。


2

为了澄清问题,防止您陷入“所有的for循环都是一样的”误区;

for(int x=0;x < ProvideCeiling(); x++)

每次调用函数时,都会执行该函数。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接