我想删除每个将delete boolean设置为true的硬币,我知道我无法从foreach迭代的同一个集合中删除它。所以我制作了一个副本(临时),但它不断抛出相同的异常:
收藏被修改;枚举操作可能无法执行。
我做错了什么?这是我的代码:
List<Coin> temp = coins;
foreach (Coin c in coins)
{
if (c.delete)
temp.Remove(c);
else
c.somethingElse();
}
coins = temp;
答案 0 :(得分:1)
这里最简单的方法是致电coins.RemoveAll(coin => coin.delete);
。 (谷歌List.RemoveAll
)。这会留下一个硬币列表,在一个语句中删除所有“删除”,无需担心计数器或循环变量或临时副本或其他任何东西。那么你可以迭代剩余的硬币做你想做的任何事情。
coins.RemoveAll(coin => coin.delete);
foreach (var coin in coins)
coin.somethingElse();
如果您需要一个只能在列表中迭代一次的更快的实现,那么这就是您需要的。不要在循环中使用Remove
或RemoveAt
的任何解决方案,因为这些操作很慢,因此在循环中使用它们将 kill 性能。下面只是将每个“好”硬币向下移动到index
位置,然后递增index
,因此任何时候所有“好”硬币都低于index
。最后你删除index
以上的所有硬币,所以你只剩下“好”的硬币。因为它只迭代一次,所以它比RemoveAll
选项快2倍,而 way 比循环中的任何解Remove
或RemoveAt
都快。< / 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;
正如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方法中非常频繁地调用),那么如果在这里生成一点点垃圾可能并不重要。