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

时间:2014-03-31 20:36:25

标签: c# foreach

我知道不修改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例外,然后我会通过它。但我仍然得到了我无法理解的例外情况。

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

1 个答案:

答案 0 :(得分:3)

  

首先我保存所有密钥

不,你没有; 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请求下一个项目时, it 要求下一个项目。是的,这听起来很复杂。好消息是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!