anon
anon

Reputation:

Best practice to avoid InvalidOperationException: Collection was modified?

Very often I need something like that:

 foreach (Line line in lines)
 {
    if (line.FullfilsCertainConditions())
    {
       lines.Remove(line)
    }
 }

This does not work, because I always get a InvalidOperationException because the Enumerator was changed during the loop.

So I changed all my loops of this kind to the following:

List<Line> remove = new List<Line>();
foreach (Line line in lines)
{
   if (line.FullfilsCertainConditions())
   {
      remove.Add(line)
   }
}

foreach (Line line in remove) {
{
   lines.Remove(line);
}

I'm not sure if this is really the best way since in the worst case I have to iterate 2 times over the original list and so it needs time 2n instead of n.

Is there a better way to do this?

EDIT:

I was able to do that using Mark's answer!But what if my collection doesn't implements RemoveAll()?

For example a

System.Windows.Controls.UIElementCollection

EDIT 2:

Again with the help of Mark I'm now able to make the following call to remove all ScatterViewItems:

CollectionUtils.RemoveAll(manager.getWindow().IconDisplay.Items, elem => elem.GetType() == typeof(ScatterViewItem));

Upvotes: 12

Views: 7725

Answers (4)

user1014875
user1014875

Reputation:

Also you can just use while loop.

int i = 0;
while(i < lines.Count)
{
  if (lines[i].FullfilsCertainConditions())
  {
     lines.RemoveAt(i);
  }
  else {i++;}
}

Upvotes: 1

Marc Gravell
Marc Gravell

Reputation: 1063338

This is baked directly into List<T>:

lines.RemoveAll(line => line.FullfilsCertainConditions());

or in C# 2.0:

lines.RemoveAll(delegate(Line line) {
    return line.FullfilsCertainConditions();
});

In the non-List<T> case (your edit to the question), you could wrap this something like below (untested):

static class CollectionUtils
{
    public static void RemoveAll<T>(IList<T> list, Predicate<T> predicate)
    {
        int count = list.Count;
        while (count-- > 0)
        {
            if (predicate(list[count])) list.RemoveAt(count);
        }
    }
    public static void RemoveAll(IList list, Predicate<object> predicate)
    {
        int count = list.Count;
        while (count-- > 0)
        {
            if (predicate(list[count])) list.RemoveAt(count);
        }
    }
}

Since UIElementCollection implements the (non-generic) IList this should work. And quite conveniently, with C# 3.0 you can add a this before IList / IList<T> and have it as an extension method. The only subtlety is that the parameter to the anon-method will be object, so you'll need to cast it away.

Upvotes: 17

Henrik Stenb&#230;k
Henrik Stenb&#230;k

Reputation: 4082

Build a new list instaed:

public IList<Line> GetListWithoutFullfilsCertainConditions(IList<Line> fullList) 
{
    IList<Line> resultList = new List<Line>(fullList.Count);

    foreach (Line line in fullList)
    {
       if (!line.FullfilsCertainConditions())
       {
          resultList.Add(line)
       }
    }

    return resultList;
}

Upvotes: 1

Victor Haydin
Victor Haydin

Reputation: 3548

You could simply replace original list with filtered one:

lines = lines.Where(line => line.FullfilsCertainConditions()).ToList();

Upvotes: 1

Related Questions