-2

众所周知,在 C# 中迭代​​某些 IEnumerable 时,不能对可枚举集合的元素进行任何修改:

// Illegal code
foreach (Employee e in employeeList)
{
     e.Salary = 1000000;
}

我想知道运行时或枚举器本身是如何强制执行的?

4

3 回答 3

7

我认为你误解了限制。您发布的代码是完全合法的,事实上,如果不是这样,那么foreach循环将毫无用处。

不允许在循环中间更改序列:foreach

foreach ( Employee e in employeeList )
{
  if ( e.Salary > 10000000 ) 
  {
    employeeList.Remove(e);
  }
}

此代码InvalidOperationException在运行时抛出一个:当它检测到底层集合在调用之间发生变化时,实现MoveNext中的调用将抛出。IEnumerator这是实现枚举器对象的要求——每个实现都IEnumerable必须自己处理这种可能性。请注意,某些集合(来自 .NET 4.0 的新并发集合)明确不强制执行此限制,因此上面的代码将是合法的employeeList,例如 a ConcurrentDictionary. 有关详细信息,请参阅此博客文章

给循环控制变量赋值也是不合法的,例如:

foreach ( Employee e in employeeList )
{
  if ( e.Salary > 10000000 ) 
  {
    e = new Employee { Salary = 999999 };
  }
}

不允许这样做的原因是因为它没有多大意义,而且几乎可以肯定是一个错误;有关详细信息,请参阅此答案:为什么 C# foreach 语句中的迭代变量是只读的?

请注意,这并不是试图改变序列中的元素——它只是试图改变用作循环控制变量的局部变量的值。此外,这不是由枚举器运行时强制执行的。这是一个编译时错误,由 C# 编译器强制执行。

于 2012-06-09T18:26:22.143 回答
2

你所说的并不完全正确。这段代码是完全合法的。

foreach (Employee e in employeeList){
     e.Salary = 1000000;
}

然而,这不是。

foreach (Employee e in employeeList){
     e = new Employee();
}
于 2012-06-09T18:26:13.343 回答
2

该限制(如其他答案中所述,您的代码没有违反)由枚举器强制执行 - 如果它选择这样做的话。我相信一些内置集合,例如List<T>保留一个内部版本号,当集合更改时更新,并在每次迭代时检查。

其他一些类,例如检查这一点,而是在集合更改时围绕枚举数的行为做出其他保证,无论是否在同一个线程中。ConcurrentBag

于 2012-06-09T18:26:56.480 回答