在 foreach 循环中修改集合时出现异常

本文关键字:异常 集合 修改 foreach 循环 | 更新日期: 2023-09-27 17:56:01

我知道不修改foreach内集合的基本原理,这就是为什么我做了这样的事情:

    public void UpdateCoverages(Dictionary<PlayerID, double> coverages)
    {
        // TODO: temp
        var keys = coverages.Select(pair => pair.Key);
        foreach (var key in keys)
        {
            coverages[key] = 0.84;
        }
    }

和:

class PlayerID : IEquatable<PlayerID>
{
    public PlayerID(byte value)
    {
        Value = value;
    }
    public byte Value { get; private set; }
    public bool Equals(PlayerID other)
    {
        return Value == other.Value;
    }
}

首先,我保存所有密钥以免出现Collection modified异常,然后通过它。但我仍然得到我无法理解的例外。

如何纠正此问题以及导致问题的原因?

在 foreach 循环中修改集合时出现异常

首先我保存所有密钥

不,你没有; keys 是一个实时序列,它在foreach迭代时主动迭代集合。若要创建密钥的独立副本,需要在末尾添加.ToList()(或类似副本):

var keys = coverages.Select(pair => pair.Key).ToList();

虽然我个人可能会选择:

var keys = new PlayerID[coverages.Count];
coverages.Keys.CopyTo(keys, 0);

(允许正确长度分配和内存复制)


实际上什么是实时序列?

Select方法在另一个方法上创建非缓冲假脱机迭代器...这是一件非常复杂的事情,但基本上:当你第一次开始迭代var key in keys时,它会抓取coverages的内部序列(又名coverages.GetEnumerator()),然后每次foreach请求下一项时,都会要求下一项。是的,这听起来很复杂。好消息是C#编译器自动内置了所有内容,并为您生成状态机等。所有这些都主要使用 yield return 语法完成。Jon Skeet 在 C# 深入的第 6 章中对此进行了很好的讨论。IIRC这曾经是"免费章节",但现在不是了。

但是,请考虑以下事项:

static IEnumerable<int> OneTwoOneTwoForever()
{
    while(true) {
        yield return 1;
        yield return 2;
    }
}

您可能会惊讶地发现,您可以使用上述内容,使用相同的非缓冲"当您请求另一个值时,它运行足够的代码来为您提供下一个值"方法:

var firstTwenty = OneTwoOneTwoForever().Take(20).ToList(); // works!