Imagine that during a
foreach(var item in enumerable)
可枚举项发生变化,是否会影响当前的foreach循环?
示例:
var enumerable = new List<int>();
enumerable.Add(1);
Parallel.ForEach<int>(enumerable, item =>
{
enumerable.Add(item + 1);
});
它会无限循环吗?
Imagine that during a
foreach(var item in enumerable)
可枚举项发生变化,是否会影响当前的foreach循环?
示例:
var enumerable = new List<int>();
enumerable.Add(1);
Parallel.ForEach<int>(enumerable, item =>
{
enumerable.Add(item + 1);
});
一般情况下,它应该抛出异常。
List<T>
的GetEnumerator()实现提供了一个Enumerator<T>
对象,其MoveNext()
方法如下(来自Reflector):
public bool MoveNext()
{
List<T> list = this.list;
if ((this.version == list._version) && (this.index < list._size))
{
this.current = list._items[this.index];
this.index++;
return true;
}
return this.MoveNextRare();
}
private bool MoveNextRare()
{
if (this.version != this.list._version)
{
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
}
this.index = this.list._size + 1;
this.current = default(T);
return false;
}
list._version
是在每次修改列表的操作中进行修改(增加)的。这取决于枚举器的性质。其中许多在集合更改时会抛出异常。
例如,List<T>
在枚举过程中如果集合发生更改,则会抛出 InvalidOperationException
异常。
IEnumerable
[和 IEnumerable<T>
--非泛型名称将同时指代两者] 的文档建议,任何时候实现这些接口的对象发生更改时,都应使其先前生成的任何 IEnumerator
[IEnumerator<T>
] 实例无效,导致它们在以后的访问尝试中引发 InvalidOperationException
。尽管微软的文档没有记录此立场的任何更改,但他们实际上对 IEnumerable
的实现似乎遵循一条更宽松的规则,即如果基础集合被修改,则 IEnumerator
不应该表现出荒谬的行为;如果它不能表现得“明智”,那么它应该抛出 InvalidOperationException
。不幸的是,由于这条规则没有明确说明,而是从它们的类的行为推断出来的,因此不清楚什么样的“明智”行为应该是什么。这完全取决于IEnumerable的实现方式。
对于List,它会抛出一个IllegalOperationException异常。但是不要依赖于此行为来处理IEnumarables。有些循环无限并且会很快抛出OutOfMemoryexception异常。