在枚举另一个集合时修改集合

时间:2013-07-25 00:00:35

标签: c# collections xna enumeration monogame

我想删除每个将delete boolean设置为true的硬币,我知道我无法从foreach迭代的同一个集合中删除它。所以我制作了一个副本(临时),但它不断抛出相同的异常:

  

收藏被修改;枚举操作可能无法执行。

我做错了什么?这是我的代码:

List<Coin> temp = coins;
foreach (Coin c in coins)
{
    if (c.delete)
        temp.Remove(c);
    else
        c.somethingElse();
}
coins = temp;

3 个答案:

答案 0 :(得分:1)

这里最简单的方法是致电coins.RemoveAll(coin => coin.delete);。 (谷歌List.RemoveAll)。这会留下一个硬币列表,在一个语句中删除所有“删除”,无需担心计数器或循环变量或临时副本或其他任何东西。那么你可以迭代剩余的硬币做你想做的任何事情。

coins.RemoveAll(coin => coin.delete);
foreach (var coin in coins)
    coin.somethingElse();

如果您需要一个只能在列表中迭代一次的更快的实现,那么这就是您需要的。不要在循环中使用RemoveRemoveAt的任何解决方案,因为这些操作很慢,因此在循环中使用它们将 kill 性能。下面只是将每个“好”硬币向下移动到index位置,然后递增index,因此任何时候所有“好”硬币都低于index。最后你删除index以上的所有硬币,所以你只剩下“好”的硬币。因为它只迭代一次,所以它比RemoveAll选项快2倍,而 way 比循环中的任何解RemoveRemoveAt都快。< / p>

var index = 0;
for (var i = 0; i < coins.Count; ++i)
{
    var coin = coins[i];
    if (!coin.delete)
    {
        coin.somethingElse();
        coins[index++] = coin;
    }
}
coins.RemoveRange(index, coins.Count - index);

答案 1 :(得分:1)

  

我做错了什么?

您的代码无效的原因是您只是将引用复制到coins变量。就像C中的指针一样,引用只不过是一个保存另一种类型的内存地址的变量。因此,temp变量指向与coins变量完全相同的集合。

  

所以我做了一个副本(临时)

您的代码打算要执行的操作(正如您所描述的)是:

List<Coin> temp = coins.ToList();
foreach (Coin c in coins)
{
    if (c.delete)
        temp.Remove(c);
    else
        c.somethingElse();
}
coins = temp;

(见Enumerable.ToList

正如Dax已经提到的,还有其他更短的方法可以达到相同的效果。在这种情况下,List.RemoveAll是一个完全可以接受的解决方案。您可能还想查看LINQ queries

答案 2 :(得分:0)

由于您使用“xna”和“monogame”标记了问题,因此您可以从避免生成垃圾的解决方案中受益(堆内存分配将触发后续垃圾收集器搅拌)。这就是我使用的,即使LINQ的魔力不可用,它也适用于具有列表类型数据结构的任何编程语言:

// Iterate the list backwards so we never skip items or accidentally
// access past the end of the list.
for (int i = coins.Count - 1; i >= 0; --i)
{
   if (coins[i].delete)
   {
      coins.RemoveAt(i);
   }
   else
   {
      coins[i].somethingElse();
   }
}

在@ DaxFohl的解决方案中,我认为创建lambda(“coin =&gt; coin.delete”)将在堆上分配垃圾内存。并且枚举也可能从堆中分配,至少在Mono中。我听说有传言称微软在Windows上的规范.NET实现具有超优化的优化w.r.t.内存使用情况,但我喜欢在我自己的游戏逻辑中安全地播放它。

另一方面,如果这不是你逻辑的内循环标本(例如在Update方法中非常频繁地调用),那么如果在这里生成一点点垃圾可能并不重要。