如何在foreach循环期间阻止修改IEnumerable <t>元素</t>

时间:2012-06-09 18:17:08

标签: c# .net clr

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

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

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

3 个答案:

答案 0 :(得分:6)

我认为你误解了限制。你发布的代码是完全合法的,事实上,如果不是那么foreach循环就没用了。

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

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

此代码在运行时抛出InvalidOperationExceptionMoveNext实现中对IEnumerator的调用将在检测到基础集合在调用之间发生更改时抛出。这是实现枚举器对象的要求 - IEnumerable的每个实现都必须为自己处理这种可能性。请注意,某些集合(来自.NET 4.0的新并发集合)明确地不强制执行此限制,因此上面的代码是合法的employeeList,例如,ConcurrentDictionary。有关详细信息,请参阅this blog post

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

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

这是不允许的原因是因为它没有多大意义,几乎可以肯定是一个bug;有关详细信息,请参阅此答案:Why is The Iteration Variable in a C# foreach statement read-only?

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

答案 1 :(得分:2)

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

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

然而,事实并非如此。

foreach (Employee e in employeeList){
     e = new Employee();
}

答案 2 :(得分:2)

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

其他一些类如ConcurrentBag 不会检查这一点,但在更改集合时对枚举器的行为做出其他保证,无论是否在同一个线程中