Foreach会缓存IEnumerable吗?

10

假设SomeMethod的签名为

public IEnumerable<T> SomeMethod<T>();

这两者之间有什么区别吗?

foreach (T tmp in SomeMethod<T>()) { ... }

并且

IEnumerable<T> result = SomeMethod<T>();

foreach (T tmp in result) { ... }

换句话说,SomeMethod<T>的结果会在第一条语句上被缓存还是在每次迭代时重新计算?
2个回答

19
假设你正在谈论C#。 foreach被转换为类似于以下内容:
var enumerable = SomeMethod<T>(); // Or whatever is passed to foreach
var enumerator = enumerable.GetEnumerator();

while (enumerator.MoveNext())
{
...
}

因此,由于 enumerable 只需要使用一次,即使您将其直接放入 foreach 语句中,也只会调用一次 SomeMethod 方法。


2
小小的挑剔:应该有一个“using”或“finally”块来确保如果枚举器实现了IDisposable接口,它将被处理。在许多情况下,特别是涉及迭代器的情况下,废弃的枚举器可能会导致严重的问题。 - supercat
2
@supercat:你说得对,但这个例子只是为了展示foreach的主要概念,而不是用于生产。foreach本身会负责处理枚举器的释放。 - František Žiačik
3
我知道这段代码是为了举例说明而设计的,但很多人并没有意识到创建实现IDisposable接口的枚举器时,为了正确性,必须确保在枚举器上调用Dispose方法。为确保正确性,唯一需要做的就是在创建枚举器时添加一个"using"块。 - supercat
@supercat:没错,一个人可能会说,它是IEnumerator,不是IDisposable,为什么我要尝试处理?很难看出如果你得到一个接口,它也可以是实现任何其他接口的对象。感谢您指出这一点。 - František Žiačik
@supercat: 当然,"using"不能与返回IEnumerator的GetEnumerator一起使用,编译器会抱怨它无法确定它是否也是IDisposable。因此,在检查是否真的是IDisposable之后,必须使用try-finally。 - František Žiačik
显示剩余2条评论

1

枚举器(enumerator)通常是与可枚举对象(enumerable)不同类型的对象。通常,枚举器将保存对可枚举对象的引用,以及有关其在枚举过程中所处位置的一些信息。可枚举对象的目的实际上并不是提供一系列项目,而是提供一个枚举器,该枚举器将进一步提供序列。

在vb.net和C#中,foreach结构通过在对象上调用GetEnumerator方法一次并抓取它返回的对象来工作,重复调用从GetEnumerator返回的对象的MoveNext和Current方法,最后,如果该对象实现了Dispose,则调用Dispose。 C#和vb.net都不会实际缓存可枚举对象,但它们在调用一次GetEnumerator之后不需要再使用该对象。两种语言都保留了枚举器,但除了隐含的MoveNext、Current和Dispose调用之外,它们都没有提供任何处理枚举器的手段。


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