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

Mr.*_*Boy 3 c# .net-2.0

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

我注意到这似乎工作正常(sourceDirInclusion很简单Dictionary<X,Y>)

    foreach (string dir in sourceDirInclusion.Keys)
    {
        if (sourceDirInclusion[dir] == null)
            sourceDirInclusion.Remove(dir);
    }
Run Code Online (Sandbox Code Playgroud)

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

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

我试图解决的问题是,sourceDirInclusion最初居住,但后来每个值可以在第二遍促进新项目的字典.例如,我想做的是:

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

cdh*_*wie 7

简短回答:这不安全.

答案很长:来自IEnumerator<T>文档:

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

请注意,文档说行为是未定义的,这意味着它可能有效,也可能不行.永远不要依赖未定义的行为.

在这种情况下,它取决于Keys可枚举的行为,关于它是否在您开始枚举时创建键列表的副本.在这个特定的情况下,我们从文档中知道返回值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)
Run Code Online (Sandbox Code Playgroud)

对此:

foreach (string dir in sourceDirInclusion.Keys.ToList())
Run Code Online (Sandbox Code Playgroud)

ToList()扩展方法将创建密钥列表中的一个明确的副本,使它修改字典安全; "基础集合"将是副本而不是原始副本.