【问题标题】:Collection was modified where items are not being modified在未修改项目的地方修改了集合
【发布时间】:2012-12-31 12:20:45
【问题描述】:

我正在使用 C# 的 XNA 库开发一个简单的游戏。在下面的代码 sn-p 我得到了

集合已修改;枚举操作可能无法执行。

第二个 foreach 循环顶部的错误。在我(相对有限的)C# 经验中,尝试在循环期间修改基础集合时会发生这种情况。但是,据我所知,我并没有以任何方式修改enemy_positions 集合。此代码中的所有集合都是List<Vector2> 类型。

这里发生了什么?

//defines collision behaviour when enemy is hit
int be_no = 0;
List<Vector2> tmp_bullets = bullet_i_position;
List<Vector2> tmp_enemy = enemy_positions;

foreach (Vector2 bullet in bullet_i_position)
{
    //get bullet collision box
    Rectangle bullet_col = new Rectangle(Convert.ToInt32(bullet.X - 12), Convert.ToInt32(bullet.Y - 12), 25, 26);

    int en_no = 0;

    foreach (Vector2 enemy in enemy_positions)
    {
        //get enemy collsion box
        en_box = new Rectangle(Convert.ToInt32(enemy.X), Convert.ToInt32(enemy.Y), 75, 75);

        if (temp_r.Intersects(en_box))
        {
            //remove all colliding elements
            tmp_enemy.RemoveAt(en_no);
            tmp_bullets.RemoveAt(be_no);
            bullet_direction.RemoveAt(be_no);

        }
        en_no++;
    }
    be_no++;
}

//update actual lists
bullet_i_position = tmp_bullets;
enemy_positions = tmp_enemy;

【问题讨论】:

    标签: c#


    【解决方案1】:

    线条:

    List<Vector2> tmp_bullets = bullet_i_position;
    List<Vector2> tmp_enemy = enemy_positions;
    

    没有克隆列表,它们只是创建对同一列表的本地引用。直接的解决方案是将这两行更改为:

    List<Vector2> tmp_bullets = new List<Vector2>(bullet_i_position);
    List<Vector2> tmp_enemy = new List<Vector2>(enemy_positions);
    

    但这将在每次调用该方法时分配一个新列表,这对于垃圾收集(尤其是在游戏中)来说是很糟糕的,因此更好的解决方案是删除您的 foreach 循环并用 reverse for 替换它们循环。这是有效的,因为只有在使用枚举器迭代集合时才会出现该异常。同样的问题不适用于常规 for 循环。例如:

    for (int i = bullet_i_position.Count - 1; i >= 0; i--)
    {
        Vector2 bullet = bullet_i_position[i];
    
        // ...
    }
    

    reverse 迭代的另一个原因是,在定期迭代时删除元素意味着您将在删除元素之后跳过该元素(因为索引向下移动 1)

    【讨论】:

    • 感谢您的即时解决方案。此外,关于循环的额外细节也很有意义,所以我将来会改变我的做法(我来自 PHP 背景,我不必过多考虑循环的类型)。
    • 是的,您唯一需要这样做的是删除元素时,因为大多数托管语言不喜欢在枚举时从枚举器/迭代器中删除元素。除此之外,foreach 循环一直有效。
    • 我使用的另一个解决方案是使用ToArray 而不是创建新列表。
    • @ashes999:ToArray 仍然分配一个新对象。它比创建一个新列表更有效,但只是非常非常轻微。避免创建任何新对象要好得多。
    • @SeanMiddleditch 这对我来说是个新闻。数组也传达了“只读集合”的含义。有一个更好的方法吗?当我看到这个异常时,我正在添加和删除。
    【解决方案2】:
    List<Vector2> tmp_enemy = enemy_positions;
    

    不复制enemy_positions 并将其分配给tmp_enemy。相反,tmp_enemy 指向enemy_positions。因此,tmp_enemy 的任何变化都会反映在enemy_positions 中。 如果你想制作一个实际的副本,这是一个更好的方法:

    List<Vector2> tmp_enemy = new List<Vector2>(enemy_positions);
    

    【讨论】:

      【解决方案3】:

      正如其他人提到的,您遇到了引用类型和值类型之间的区别

      我建议您将 foreach 循环替换为类似于以下内容的内容

      for(int i = 0; i<bullet_i_position.count;i++)
        {
           bool hascollided = false;
           for(int j = 0; j<enemy_positions.count;j++)
              {
                if(collisionOccurs)
                     {
                      hasCollided = true;
                      enemy_positions.RemoveAt(j);
                      j--;
                     }
             }
         if(hasCollided)
            {
               bullet_i_position.RemoveAt(i);
               i--;
            }
      }
      

      你需要考虑如果子弹在上面的例子中同时撞到两个敌人会发生什么

      【讨论】:

      • Robert Rouhani 建议的反向迭代过程要优雅得多;在 for 循环中更改迭代器的值通常被认为是不好的做法,需要重构。
      • 确实如此,尽管您仍然需要注意不要过早移除对象
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-05-14
      • 1970-01-01
      • 1970-01-01
      • 2011-04-07
      • 2012-04-30
      • 1970-01-01
      相关资源
      最近更新 更多