在C#中,当枚举容器被修改时,foreach的行为如何

时间:2013-01-24 17:38:49

标签: c# .net-2.0

这似乎应该得到回答,但我发现潜在的欺骗行为会提出不同的事情......

我注意到这似乎工作正常(sourceDirInclusion是一个简单的Dictionary<X,Y>

    foreach (string dir in sourceDirInclusion.Keys)
    {
        if (sourceDirInclusion[dir] == null)
            sourceDirInclusion.Remove(dir);
    }

这是否意味着从foreach中删除集合中的项目是安全的,还是我很幸运?

如果我在字典中添加更多元素而不是删除?

我正在尝试解决的问题是最初填充sourceDirInclusion,但是每个值都可以在第二遍中为字典提供新项目。例如,我想做的是:

foreach (string dir in sourceDirInclusion.Keys)
{
  X x = sourceDirInclusion[dir];
  sourceDirInclusion.Add(X.dir,X.val);
}

4 个答案:

答案 0 :(得分:7)

简短回答:这不安全。

答案很长:来自IEnumerator<T> documentation

  

只要集合保持不变,枚举器仍然有效。如果对集合进行了更改,例如添加,修改或删除元素,则枚举数将无法恢复,并且其行为未定义。

请注意,文档说行为是未定义的,这意味着它可能有效,也可能不行。人们不应该依赖未定义的行为。

在这种情况下,它取决于Keys可枚举的行为,关于它是否在您开始枚举时创建了键列表的副本。在这种特定情况下,我们从the docs知道Dictionary<,>.Keys的返回值是一个引用回字典的集合:

  

返回的Dictionary<TKey, TValue>.KeyCollection不是静态副本;相反,Dictionary<TKey, TValue>.KeyCollection会引用原始Dictionary<TKey, TValue>中的键。因此,对Dictionary<TKey, TValue>的更改将继续反映在Dictionary<TKey, TValue>.KeyCollection

因此,在枚举字典的键时修改字典应该被认为是不安全的。

您可以通过一次更改来纠正此问题。改变这一行:

foreach (string dir in sourceDirInclusion.Keys)

对此:

foreach (string dir in sourceDirInclusion.Keys.ToList())

ToList()扩展方法将创建密钥列表的显式副本,从而可以安全地修改字典; “基础馆藏”将是副本,而不是原件。

答案 1 :(得分:3)

如果将抛出

  

InvalidOperationException:Message =“Collection已被修改;枚举操作可能无法执行

避免将候选人添加到外部列表中。然后循环它并从目标容器(字典)中删除。

List<string> list = new List<string>(sourceDirInclusion.Keys.Count);
foreach (string dir in sourceDirInclusion.Keys)
{
    if (sourceDirInclusion[dir] == null)
        list.Add(dir);
}
foreach (string dir in list)
{
    sourceDirInclusion.Remove(dir);
}

答案 2 :(得分:0)

检查一下:What is the best way to modify a list in a 'foreach' loop?

简而言之:

The collection used in foreach is immutable. This is very much by design.

正如MSDN上所说:

foreach语句用于遍历集合以获取所需的信息,但不能用于添加或删除源集合中的项目以避免不可预测的副作用。如果需要在源集合中添加或删除项目,请使用for循环。

<强>更新 您可以改为使用for循环:

for (int index = 0; index < dictionary.Count; index++) {
  var item = dictionary.ElementAt(index);
  var itemKey = item.Key;
  var itemValue = item.Value;
}

答案 3 :(得分:0)

这是有效的,因为您正在遍历sourceDirInclusion.Keys。

但是,为了确保未来版本的FrameWork,我建议您在foreach语句中使用sourceDirInclusion.Keys.ToArray(),这样您就可以创建循环键的副本。

但这不起作用:

foreach(KeyValuePair<string, object> item in sourceDirInclusion)
{
    if (item.Value == null)
        sourceDirInclusion.Remove(item.Key);
}

通常,您无法在遍历集合时修改集合,但通常可以使用.ToArray()或.ToList()创建新集合,并在修改原始集合时遍历该集合。

祝你好运。