Can you remove an item from a List<> whilst ite

2020-01-31 03:59发布

Can you remove an item from a List<> whilst iterating through it? Will this work, or is there a better way to do it?

My code:

foreach (var bullet in bullets)
  {
    if (bullet.Offscreen())
    {
      bullets.Remove(bullet);
    }
  }

-edit- Sorry guys, this is for a silverlight game. I didn't realise silverlight was different to the Compact Framework.

7条回答
放荡不羁爱自由
2楼-- · 2020-01-31 04:26

It is better to either create a list that will contain items to remove, then remove items from the list:

List<Bullet> removedBullets = new List<Bullet>();

foreach(var bullet in bullets)
{
  if (bullet.OffScreen())
  {
   removedBullets.Add(bullet);
  }
}

foreach(var bullet in removedBullets)
{
  bullets.Remove(bullet);
}
查看更多
乱世女痞
3楼-- · 2020-01-31 04:29

Try this:

bullets.RemoveAll(bullet => bullet.Offscreen());
查看更多
啃猪蹄的小仙女
4楼-- · 2020-01-31 04:30

Attempting to remove it within a foreach loop will throw an exception. You need to iterate through it backwards with a for loop.

for (int count = bullets.Count - 1; count >= 0; count--)
{
  if (bullets[count].Offscreen())
    {
        //bullets.Remove(bullets[count]);
        bullets.RemoveAt(count);
    }
}
查看更多
何必那么认真
5楼-- · 2020-01-31 04:32

I've come across this problem before and blogged about it here.

Short version is that you can create an extension method called RemoveIf:

public void RemoveIf<T>(ICollection<T> collection, Predicate<T> match)
{
    List<T> removed = new List<T>();
    foreach (T item in collection)
    {
        if (match(item))
        {
            removed.Add(item); 
        }
    }

    foreach (T item in removed)
    {
        collection.Remove(item);
    }

    removed.Clear();
}

And then just call it with your delegate each time you need it:

RemoveIf(_Entities.Item, delegate(Item i) { return i.OffScreen(); });
查看更多
来,给爷笑一个
6楼-- · 2020-01-31 04:40

Edit: to clarify, the question is regarding Silverlight, which apparently does not support RemoveAll on List`T. It is available in the full framework, CF, XNA versions 2.0+

You can write a lambda that expresses your removal criteria:

bullets.RemoveAll(bullet => bullet.Offscreen());

Or you can select the ones you do want, instead of removing the ones you don't:

bullets = bullets.Where(b => !b.OffScreen()).ToList();

Or use the indexer to move backwards through the sequence:

for(int i=bullets.Count-1;i>=0;i--)
{
    if(bullets[i].OffScreen())
    {
        bullets.RemoveAt(i);
    }
}
查看更多
仙女界的扛把子
7楼-- · 2020-01-31 04:41
bullets.RemoveAll(bullet => bullet.Offscreen());

Edit: To make this work as-is in silverlight, add the following extension method to your project.

Like List<T>.RemoveAll, this algorithm is O(N) where N is the length of the list as opposed to O(N*M) where M is the number of elements removed from the list. Since it's an extension method with the same prototype as the RemoveAll method found in non-Silverlight frameworks, the built-in one will be used when available, and this one used seamlessly for silverlight builds.

public static class ListExtensions
{
    public static int RemoveAll<T>(this List<T> list, Predicate<T> match)
    {
        if (list == null)
            throw new NullReferenceException();

        if (match == null)
            throw new ArgumentNullException("match");

        int i = 0;
        int j = 0;

        for (i = 0; i < list.Count; i++)
        {
            if (!match(list[i]))
            {
                if (i != j)
                    list[j] = list[i];

                j++;
            }
        }

        int removed = i - j;
        if (removed > 0)
            list.RemoveRange(list.Count - removed, removed);

        return removed;
    }
}
查看更多
登录 后发表回答