我不明白为什么List<T>.ForEach()
扩展方法会在引擎盖下实现for
循环。这开辟了收集被修改的可能性。正常foreach
会在这种情况下抛出异常,所以ForEach()
肯定会以同样的方式做出反应吗?
如果你因任何原因必须改变一个集合,那么你肯定应该在for
循环中手动迭代集合吗?
foreach
和List<T>.ForEach()
之间似乎存在一些语义矛盾。
我错过了什么吗?
答案 0 :(得分:5)
因为List.ForEach遵循MSDN中的定义:
对List的每个元素执行指定的操作。
这意味着对元素执行的Action
可能会更改元素,或集合本身。在这种情况下,没有其他方法(如果没有创建costy克隆集合,如果可能的话)支付这个,然后使用简单的for
。
如果在foreach
的迭代过程中更改了集合,它自然会引发异常。
答案 1 :(得分:5)
foreach
是一个C#语言元素。它遵循C#的规则。
List<T>.ForEach
是一种.NET Framework方法。它遵循.NET规则,其中foreach
不存在。
这是“语言与框架”混淆的一个例子。框架方法必须以多种语言工作,语言(通常)具有矛盾的语义。
这种“语言与框架”混淆的另一个例子是.net 3和.NET 3.5之间对Enumerable.Cast
的重大改变。在.NET 3中,Cast
使用了C#语义。在.net 3.5中,它被改为使用.net语义。
答案 2 :(得分:4)
只有BCL团队的成员可以肯定地告诉我们,但这可能仅仅是List<T>.ForEach
允许您修改列表的疏忽。
首先,David B的答案对我没有意义。它是List<T>
,而不是C#,它会检查您是否在foreach
循环内修改了列表,如果这样做,则会抛出InvalidOperationException
。它与你正在使用的语言无关。
其次,documentation:
中有此警告修改Action&lt; T&gt;正文中的基础集合。委托不受支持并导致未定义的行为。
我发现BCL团队不太可能希望像ForEach
这样的简单方法具有未定义的行为。
第三,从.NET 4.5开始,如果委托修改了列表,List<T>.ForEach
将抛出InvalidOperationException
。如果程序依赖于旧行为,it will stop working何时将其重新编译为目标.NET 4.5。微软愿意接受这一突破性变化的事实强烈表明,原始行为是无意的,不应该依赖。
供参考,以下是直接来自参考源的.NET 4.0中List<T>.ForEach
的实现方式:
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);
}