为什么列出<T>。ForEach() 实现一个 for 循环?



我不明白为什么List<T>.ForEach()扩展方法在后台实现for循环。这打开了修改集合的可能性。在这种情况下,正常的foreach会抛出异常,那么ForEach()肯定也应该以同样的方式做出反应吗?

如果出于任何原因必须对集合进行变异,那么您肯定应该在for循环中手动迭代该集合吗?

这里foreachList<T>.ForEach()之间似乎有点语义矛盾。

我是不是错过了什么?

只有BCL团队的一名成员可以肯定地告诉我们,但List<T>.ForEach允许您修改列表可能只是疏忽。

首先,David B的答案对我来说没有意义。是List<T>,而不是C#,它检查你是否在foreach循环中修改了列表,如果你修改了,就会抛出InvalidOperationException。这与你使用的语言无关。

其次,文档中有这样的警告:

修改Action主体中的基础集合<T>委托不受支持,并导致未定义的行为。

我发现BCL团队不太可能希望像ForEach这样的简单方法具有未定义的行为。

第三,从.NET 4.5开始,如果委托修改列表,List<T>.ForEach将抛出一个InvalidOperationException。如果一个程序依赖于旧的行为,当它被重新编译到目标.NET 4.5时,它将停止工作。微软愿意接受这一突破性的改变,这一事实有力地表明,最初的行为是无意的,不应该被依赖。

作为参考,以下是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]);
    }
}

以下是它在.NET4.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);
}

foreach是一个C#语言元素。它遵循C#的规则。

List<T>.ForEach是一个.NET Framework方法。它遵循.NET的规则,而foreach并不存在。

这是"语言与框架"混淆的一个例子。框架方法必须在许多语言中工作,并且这些语言(通常)具有矛盾的语义。

这种"语言与框架"混淆的另一个例子是.net 3和.net 3.5之间对Enumerable.Cast的突破性更改。在.NET3中,Cast使用了C#语义。在.net 3.5中,它被更改为使用.net语义。

因为List.ForEach遵循MSDN中的定义:

对列表的每个元素执行指定的操作。

这意味着在元素上执行的Action可能会更改元素或集合本身。在这种情况下,没有其他方法(如果不创建成本高昂的克隆集合,如果是可能的)可以提供此功能,然后使用简单的for

如果在foreach中的迭代过程中更改集合,它自然会引发异常。

最新更新