因为List.ForEach遵循MSDN的定义:
对List的每个元素执行指定的操作.
这意味着Action在元素上执行,可能会更改元素或集合本身.在这种情况下,没有其他方法(如果没有创建costy克隆集合,如果可能的话)支付这个,然后使用简单for.
如果在迭代期间更改集合foreach,它自然会引发异常.
foreach是一个C#语言元素.它遵循C#的规则.
List<T>.ForEach是一种.NET Framework方法.它遵循.NET规则,foreach不存在.
这是"语言与框架"混淆的一个例子.框架方法必须以多种语言工作,并且语言(通常)具有矛盾的语义.
这种"语言与框架"混淆的另一个例子是Enumerable.Cast.net 3和.NET 3.5之间的重大变化.在.NET 3中,Cast使用了C#语义.在.net 3.5中,它被改为使用.net语义.
只有 BCL 团队的一名成员可以肯定地告诉我们,但这可能只是List<T>.ForEach让您修改列表的疏忽。
首先,大卫 B 的回答对我来说没有意义。它是List<T>,而不是 C#,它会检查您是否在foreach循环中修改了列表,并在您修改时抛出一个InvalidOperationException。它与您使用的语言无关。
其次,文档中有这个警告:
不支持修改 Action<T> 委托主体中的基础集合,并会导致未定义的行为。
我发现 BCL 团队不太可能想要一个像ForEach未定义行为这样简单的方法。
第三,从 .NET 4.5 开始,如果委托修改列表,List<T>.ForEach 将抛出一个InvalidOperationException。如果程序依赖于旧行为,则在重新编译以面向 .NET 4.5 时它将停止工作。Microsoft 愿意接受这一重大更改这一事实强烈表明,最初的行为是无意的,不应依赖。
作为参考,以下List<T>.ForEach是在 .NET 4.0 中实现的方法,直接来自参考源:
public void ForEach(Action<T> action) {
if( action == null) {
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
}
Contract.EndContractBlock();
for(int i = 0 ; i < _size; i++) {
action(_items[i]);
}
}
Run Code Online (Sandbox Code Playgroud)
以下是 .NET 4.5 中的更改方式:
public void ForEach(Action<T> action) {
if( action == null) {
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
}
Contract.EndContractBlock();
int version = _version;
for(int i = 0 ; i < _size; i++) {
if (version != _version && BinaryCompatibility.TargetsAtLeast_Desktop_V4_5) {
break;
}
action(_items[i]);
}
if (version != _version && BinaryCompatibility.TargetsAtLeast_Desktop_V4_5)
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
}
Run Code Online (Sandbox Code Playgroud)