想象一下,在一个
foreach(var item in enumerable)
可枚举项发生变化。会影响当前的foreach吗?
例子:
var enumerable = new List<int>();
enumerable.Add(1);
Parallel.ForEach<int>(enumerable, item =>
{
enumerable.Add(item + 1);
});
它会永远循环吗?
想象一下,在一个
foreach(var item in enumerable)
可枚举项发生变化。会影响当前的foreach吗?
例子:
var enumerable = new List<int>();
enumerable.Add(1);
Parallel.ForEach<int>(enumerable, item =>
{
enumerable.Add(item + 1);
});
它会永远循环吗?
一般来说,它应该抛出一个异常。GetEnumerator()的List<T>
实现提供了一个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
Microsoft 的IEnumerable
[and IEnumerable<T>
--non-generic names will refer to both] 文档建议,任何时候更改实现这些接口的对象时,它都应该使之前生成的IEnumerator
[ ] 的任何实例无效,从而导致它们在未来的访问尝试IEnumerator<T>
中抛出InvalidOperationException
. 尽管 Microsoft 的文档中没有任何文件记录了这种立场的任何变化,但他们的实际实现IEnumerable
似乎遵循更宽松的规则,即IEnumerator
如果底层集合被修改,则不应表现得毫无意义;如果它不能“明智地”表现,它应该抛出InvalidOperationException
. 不幸的是,由于该规则没有明确说明,而是从他们的类的行为中推断出来的,所以不清楚“明智的”行为到底应该意味着什么。
我所知道的所有 Microsoft 类如果不能满足以下条件,则会在更改集合时抛出异常:
如果集合可以通过某种方式报告它们是否可以满足上述标准(不抛出异常),即使在修改时也会有所帮助,因为它们在尝试删除所有满足特定标准的项目时非常有用。
这完全取决于 IEnumerable 是如何实现的。
使用 List,它会抛出 IllegalOperationException。但是不要依赖 IEnumarables 的这种行为。有些循环无穷无尽,会很快抛出 OutOfMemory 异常。