Foreach 可能会导致分配,但至少在较新版本的 .NET 和 Mono 中,如果您正在处理具体System.Collections.Generic
类型或数组,则不会。这些编译器的旧版本(例如 Unity3D 在 5.5 之前使用的 Mono 版本)总是生成分配。
C# 编译器使用鸭子类型来查找GetEnumerator()
方法并在可能的情况下使用该方法。类型上的大多数GetEnumerator()
方法System.Collection.Generic
都有返回结构的 GetEnumerator() 方法,并且对数组进行了特殊处理。如果您的GetEnumerator()
方法没有分配,您通常可以避免分配。
但是,如果您正在处理接口IEnumerable
、IEnumerable<T>
或. 即使您的实现类返回一个结构,该结构也将被装箱并强制转换为or ,这需要分配。IList
IList<T>
IEnumerator
IEnumerator<T>
注意:自从 Unity 5.5 更新到 C# 6 后,我知道当前没有编译器版本仍然具有第二个分配。
还有第二个分配,理解起来有点复杂。采取这个 foreach 循环:
List<int> valueList = new List<int>() { 1, 2 };
foreach (int value in valueList) {
// do something with value
}
直到 C# 5.0,它都扩展为这样的东西(有一些小的差异):
List<int>.Enumerator enumerator = valueList.GetEnumerator();
try {
while (enumerator.MoveNext()) {
int value = enumerator.Current;
// do something with value
}
}
finally {
IDisposable disposable = enumerator as System.IDisposable;
if (disposable != null) disposable.Dispose();
}
WhileList<int>.Enumerator
是一个结构,不需要在堆上分配,强制转换enumerator as System.IDisposable
将结构装箱,这是一个分配。规范随着 C# 5.0 更改,禁止分配,但 .NET打破了规范并更早地优化了分配。
这些分配非常少。请注意,分配与内存泄漏非常不同,使用垃圾收集,您通常不必担心它。但是,在某些情况下,您甚至会关心这些分配。我从事 Unity3D 工作,直到 5.5,我们无法在每个游戏帧发生的操作中进行任何分配,因为当垃圾收集器运行时,您会遇到明显的颠簸。
请注意,数组上的 foreach 循环经过特殊处理,不必调用 Dispose。据我所知,foreach 在遍历数组时从未分配过。