我不明白为什么List<T>.ForEach()
扩展方法在后台实现了一个for
循环。这打开了修改集合的可能性。在这种情况下,正常人foreach
会抛出异常,所以肯定ForEach()
应该以同样的方式做出反应吗?
如果您出于某种原因必须改变集合,那么您肯定应该在循环中手动迭代集合for
吗?
foreach
和之间似乎存在一些语义矛盾List<T>.ForEach()
。
我错过了什么吗?
因为List.ForEach遵循 MSDN 的定义:
对 List 的每个元素执行指定的操作。
这意味着Action
在元素上执行,可能会改变元素或集合本身。在这种情况下,没有其他方法(如果不创建昂贵的克隆集合,如果可能的话)负担得起,然后使用简单的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]);
}
}
以下是 .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);
}