当在枚举过程中项目发生改变时,是否会影响枚举?

8

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);
});

它会无限循环吗?
4个回答

19

一般情况下,它应该抛出异常。 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 是在每次修改列表的操作中进行修改(增加)的。

5

这取决于枚举器的性质。其中许多在集合更改时会抛出异常。

例如,List<T> 在枚举过程中如果集合发生更改,则会抛出 InvalidOperationException 异常。


2
微软关于 IEnumerable [和 IEnumerable<T> --非泛型名称将同时指代两者] 的文档建议,任何时候实现这些接口的对象发生更改时,都应使其先前生成的任何 IEnumerator [IEnumerator<T>] 实例无效,导致它们在以后的访问尝试中引发 InvalidOperationException。尽管微软的文档没有记录此立场的任何更改,但他们实际上对 IEnumerable 的实现似乎遵循一条更宽松的规则,即如果基础集合被修改,则 IEnumerator 不应该表现出荒谬的行为;如果它不能表现得“明智”,那么它应该抛出 InvalidOperationException。不幸的是,由于这条规则没有明确说明,而是从它们的类的行为推断出来的,因此不清楚什么样的“明智”行为应该是什么。
我所知道的所有微软类都会在集合更改时抛出异常,如果它们无法满足以下条件:
1.在枚举过程中存在未修改的任何项将被返回一次。
2.在枚举期间添加或删除的项目最多只返回一次,但如果对象在枚举期间被删除并重新添加,则每个重新添加可以视为创建一个新的“项”。
3.如果集合保证以排序序列返回内容,则即使插入和删除项目,也必须满足该保证 [例如,如果将“Fred”添加到按字母顺序排序的列表中,并且“George”已经被枚举,那么在该枚举过程中不得出现“Fred”]。
如果集合在修改后仍能满足上述条件(而不会引发异常),则有一些方法可以报告它们是否能够满足这些条件,因为当尝试删除满足某个条件的所有项时,它们非常有用。

1

这完全取决于IEnumerable的实现方式。

对于List,它会抛出一个IllegalOperationException异常。但是不要依赖于此行为来处理IEnumarables。有些循环无限并且会很快抛出OutOfMemoryexception异常。


Dykam:不行。它会立即失败。 - Mehrdad Afshari

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