views:

435

answers:

3

In my application, _collection is a List from which I need to remove all User objects which do not match the criteria.

However, the following code gets an invalid operation error in its second iteration since the _collection itself has been changed:

foreach (User user in _collection)
{
    if (!user.IsApproved())
    {
        _collection.Remove(user);
    }
}

I could create another List collection and copy them back and forth but then I have the issue of non-cloned reference types, etc.

Is there a way to do the above more elegantly than copying _collection to another another List variable?

+53  A: 
_collection.RemoveAll(user => !user.IsApproved());

If you're still on 2.0:

_collection.RemoveAll(delegate(User u) { return !u.IsApproved(); });

By the way, if you don't want to touch the original list, you can get another list of approved users with:

_collection.FindAll(user => user.IsApproved());
Mehrdad Afshari
+1 for elegance.
Steven Sudit
+1 this is by far the best way
JoshBerke
didn't occur to me, I don't think in lambda yet, +1 for speed and elegance
Edward Tanguay
Or `_collection.FindAll(user => user.IsApproved());` to eliminate negations.
Ionuț G. Stan
_collection.FindAll will return **another** collection. It's quite different from changing the initial collection (there might be cases when some other objects hold references to the same list and you need that one changing, rather than creating another).
Mehrdad Afshari
@Mehrdad, I see. Thanks for the heads up.
Ionuț G. Stan
+1 for lambda 15 chars
JonH
+4  A: 

You can always start at the top index and iterate downward towards 0:

for (int i = _collection.Count - 1; i >= 0; i--)
{
    User user = _collection[i];
    if (!user.IsApproved())
    {
        _collection.RemoveAt(i);
    }
}

Mehrdad's answer looks pretty darn elegant, though.

Dan Tao
This appears to contain a bug. Consider the case of a collection with two elements, the first of which is not approved. The first is removed, so collection[1] becomes outs of bounds.
Steven Sudit
Steven: In the case you describe the above code would have already checked collection[1] (which after removing collection[0] becomes collection[0]) since it is iterating from the top down. So there shouldn't be any out-of-bounds error.
Dan Tao
It's fine. Walk through it again and you'll see it's okay.
clintp
For those of us still on .NET 2.0, this method is the way to go.
C-Pound Guru
@C-Pound Guru: `List<T>.RemoveAll` has been there since 2.0: `list.RemoveAll(delegate(User u) { return !u.IsApproved(); });`
Mehrdad Afshari
A: 

Whenever there is a chance that a collection will be modified in a loop, opt for a for loop instead. The solution given by Mehrdad is lovely and definitely worth a try!

Here's code I find helpful when dealing with modifiable collections:

for(int index=0;index < _collection.Count; index++)
{
    if (!_collection[index].IsApproved)
    {
        _collection.RemoveAt(index);
        index--;
    }
}
Mike J