众所周知,在C#中迭代一些IEnumerable时,不能对可枚举集合的元素进行修改:
// Illegal code
foreach (Employee e in employeeList)
{
e.Salary = 1000000;
}
我想知道运行时或枚举器本身是如何强制执行的?
答案 0 :(得分:6)
我认为你误解了限制。你发布的代码是完全合法的,事实上,如果不是那么foreach
循环就没用了。
在foreach
循环中间更改序列是不允许的:
foreach ( Employee e in employeeList )
{
if ( e.Salary > 10000000 )
{
employeeList.Remove(e);
}
}
此代码在运行时抛出InvalidOperationException
:MoveNext
实现中对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
不会检查这一点,但在更改集合时对枚举器的行为做出其他保证,无论是否在同一个线程中