为什么我们不能在枚举其键时更改字典的值?

Vit*_*liy 26 c# dictionary enumeration invalidoperationexception

class Program
    {
        static void Main(string[] args)
        {
            var dictionary = new Dictionary<string, int>()
            {
                {"1", 1}, {"2", 2}, {"3", 3}
            };

            foreach (var s in dictionary.Keys)
            {
                // Throws the "Collection was modified exception..." on the next iteration
                // What's up with that?

                dictionary[s] = 1;  

            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

我完全理解为什么在枚举列表时抛出此异常 - 在枚举期间,枚举对象的结构不会改变似乎是合理的.但是,更改字典的值会改变其结构吗?具体来说,其键的结构?

Jar*_*Par 19

因为值和键存储为一对.键和值没有单独的结构,而是单个结构,它将两者都存储为一对配对值.更改值时,必须更改包含键和值的单个基础结构.

更改值是否必然会改变基础结构的顺序?不是.但这是一个特定于实现的细节Dictionary<TKey,TValue>,正确地认为类是通过允许修改值作为API的一部分来揭示这一点.

  • 杰瑞德 - 我会完全相反; 抛出异常IS暴露实现细节.一个*正确的*抽象不会告诉你.Keys集合已经改变,而事实上它没有. (11认同)
  • 我认为假设它们被认为是同一个对象的一部分并不是微不足道的(除非你已经看过代码).事实是,我熟悉的哈希表的大多数实现都不会将键和值存储为成对.并且该结构确实能够在不改变键结构的情况下改变值. (6认同)

Str*_*ior 7

实际上,我看到你来自哪里.这里没有注意到的大多数答案是你在Keys列表中迭代,而不是字典的项目本身.如果.NET框架程序员想要,他们可以相当容易地区分对字典结构所做的更改以及对字典中的值所做的更改.然而,即使人们遍历集合的密钥,他们通常也会最终获得价值.我怀疑.NET框架设计师认为,如果你在迭代这些值,你会想知道是否有什么东西正在改变它们,就像任何List一样.无论是那个还是他们都认为这不是一个重要的问题,值得进行编程和维护,以区分一种变化和另一种变化.


Dol*_*hin 7

感谢Vitaliy,我回过头来看了一下代码,看起来它是一个特定的实现决定,不允许这样做(参见下面的代码片段).字典保留一个名为verrsion的私有值,在更改现有项的值时会增加该值.创建枚举数时,它会记下当时的值,然后检查每次调用MoveNext.

for (int i = this.buckets[index]; i >= 0; i = this.entries[i].next)
{
    if ((this.entries[i].hashCode == num) && this.comparer.Equals(this.entries[i].key, key))
    {
        if (add)
        {
            ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_AddingDuplicate);
        }
        this.entries[i].value = value;
        this.version++;
        return;
    }
}
Run Code Online (Sandbox Code Playgroud)

我不知道为什么这是必要的.您仍然可以自由修改值的属性,只是不将其分配给新值:

public class IntWrapper
{
  public IntWrapper(int v) { Value = v; }
  public int Value { get; set; }
}

class Program
{
  static void Main(string[] args)
  {
    var kvp = new KeyValuePair<string, int>("1",1);
    kvp.Value = 17;
    var dictionary = new Dictionary<string, IntWrapper>(){
      {"1", new IntWrapper(1)}, 
      {"2", new IntWrapper(2)}, 
      {"3", new IntWrapper(3)} };

    foreach (var s in dictionary.Keys)
    {
      dictionary[s].Value = 1;  //OK
      dictionary[s] = new IntWrapper(1); // boom
    }
  } 
}
Run Code Online (Sandbox Code Playgroud)

  • 好吧,我查看了实现,虽然字典确实有一个条目集合,但为什么值不能更改是不明显的.虽然KeyValuePair是一个结构,但它本质上不是不可变的,正如你所说,因此它的Value属性可以改变.此外,考虑到在枚举期间更改值时给定实现抛出的任何原因,此行为不直观,并通过公开实现细节来打破封装.有人会说这只是简单的错误的API设计! (2认同)

Joh*_*ica 5

您可能刚刚在字典中插入了一个新密钥,这确实会发生变化dictionary.Keys.即使在这个永远不会发生的特定循环中,[]操作通常也可以更改密钥列表,因此将其标记为变异.


Pav*_*aev 5

Indexer onDictionary可能是一种可以更改集合结构的操作,因为如果该键不存在,它将添加一个具有此类键的新条目。这显然不是这里的情况,但我希望Dictionary契约故意保持简单,因为对象上的所有操作都分为“变异”和“非变异”,并且所有“变异”操作都使枚举器无效,即使它们实际上不要改变任何东西。