【问题标题】:Why does List<T>.ForEach() implement a for loop?为什么 List<T>.ForEach() 实现 for 循环?
【发布时间】:2012-07-14 15:39:51
【问题描述】:

我不明白为什么List&lt;T&gt;.ForEach() 扩展方法在底层实现了for 循环。这打开了修改集合的可能性。在这种情况下,普通的foreach 会抛出异常,所以ForEach() 肯定会以同样的方式做出反应吗?

如果您出于某种原因必须改变集合,那么您肯定应该在 for 循环中手动迭代集合吗?

foreachList&lt;T&gt;.ForEach() 之间似乎存在一些语义矛盾。

我错过了什么吗?

【问题讨论】:

标签: c# .net list foreach


【解决方案1】:

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

首先,David B 的回答对我来说没有意义。它是 List&lt;T&gt;,而不是 C#,它检查您是否在 foreach 循环中修改了列表,如果这样做,则抛出 InvalidOperationException。它与您使用的语言无关。

其次,documentation中有这个警告:

不支持修改 Action 委托主体中的基础集合,这会导致未定义的行为。

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

第三,从 .NET 4.5 开始,List&lt;T&gt;.ForEach 在代理修改列表时抛出 InvalidOperationException。如果一个程序依赖于旧的行为,it will stop working 当它重新编译为目标 .NET 4.5 时。 Microsoft 愿意接受这一重大更改这一事实强烈表明最初的行为是无意的,不应依赖。

作为参考,下面是 List&lt;T&gt;.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);
}

【讨论】:

  • @nawfal:可能不会。检查if (version != _version) 的成本不太可能是可衡量的。
  • 实际上,据记录,不仅在修改列表时抛出异常,即使“调用集合中的元素被修改”也是如此。太疯狂了。为什么要故意破坏良性行为?
【解决方案2】:

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

List&lt;T&gt;.ForEach 是一种 .NET Framework 方法。它遵循 .NET 的规则,其中 foreach 不存在。

这是一个“语言与框架”混淆的例子。框架方法必须适用于多种语言,并且这些语言(通常)具有相互矛盾的语义。

这种“语言与框架”混淆的另一个例子是 .net 3 和 .NET 3.5 之间对 Enumerable.Cast 的重大更改。在 .NET 3 中,Cast 使用 C# 语义。在 .net 3.5 中,它被更改为使用 .net 语义。

【讨论】:

    【解决方案3】:

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

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

    这意味着在元素上执行Action 可能会改变元素,或集合本身。在这种情况下,没有其他方法(如果不创建昂贵的克隆集合,如果有可能)负担得起,那么使用简单的for

    如果您在 foreach 的迭代期间更改集合,它自然会引发异常。

    【讨论】:

    • 啊,那么根据定义它是正确的。我仍然觉得这有点误导。我想这就是所有争议的原因(以及他们从 .NET Metro 中删除它的原因,正如 BoltClock 指出的那样)?
    • @davenewza 不,链接的文档说 Modificare la raccolta sottostante nel corpo di Action&lt;T&gt; il delegato non è supportato e non[?] causa un comportamento indefinito。 或者,如果出于某种原因更喜欢英语:不支持修改 Action&lt;T&gt; 委托主体中的基础集合并导致未定义的行为。 所以他们说你不应该修改 List&lt;&gt; 中的action 代表。
    猜你喜欢
    • 2016-08-27
    • 2017-07-14
    • 2010-12-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-26
    • 2010-10-19
    • 1970-01-01
    相关资源
    最近更新 更多