Reputation: 40641
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.
Upvotes: 4
Views: 3298
Reputation: 99949
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;
}
}
Upvotes: 17
Reputation: 144162
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);
}
}
Upvotes: 10
Reputation: 96507
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);
}
}
Upvotes: 4
Reputation: 1057
Iterate in "for" loop rather then iterating through foreach. This will work.
Upvotes: 2
Reputation: 4757
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(); });
Upvotes: 1
Reputation: 17729
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);
}
Upvotes: 2