views:

85

answers:

6

Per the title - are there good built-in options in C#/.NET for fail-safe iteration over an IList or an IDictionary?

Where I'm running into problems is with code similar to the following:

IList<Foo> someList = new List<Foo>();

//...

foreach (Foo foo in someList) {
  if (foo.Bar) {
    someList.remove(foo);
  }
}

which throws the following after the first time foo.Bar is true:

Type: System.InvalidOperationException
Message: Collection was modified; enumeration operation may not execute.

I know the easy workaround is to do foreach (Foo foo in new List<Foo>(someList)), but it's annoying to have to remember to do that every. single. time. this comes up.

Coming from a Java background, that's something that can be handled neatly with a CopyOnWriteArrayList/ConcurrentHashMap (I'm aware that there are other penalties associated with using those lists.) Is there an equivalent in C# that I'm just not aware of?

+2  A: 

If you're using .NET 4, there's ConcurrentDictionary<TKey, TValue> and ConcurrentBag<T> (and a queue and a stack in the same namespace). There's nothing which implements IList<T> as far as I'm aware though.

Jon Skeet
Sadly, we're only on .NET 3.5 for now - but that's exactly what I was looking for. Thanks!
Sbodd
A: 

A simple way to resolve your issue with modifying the collection would be to query the list for the items to remove and the iterate over the list of these items:

using System.Collections.Generic;
using System.Linq;

class Foo
{
    public bool Bar { get; set; }
}

class Program
{
    static void Main()
    {
        IList<Foo> someList = new List<Foo>() {
            new Foo() { Bar = true },
            new Foo() { Bar = false },
            new Foo() { Bar = true }
        };

        var itemsToRemove = someList.Where(f => f.Bar == true).ToArray();

        foreach (var foo in itemsToRemove)
        {
            someList.Remove(foo);
        }
    }
}
0xA3
Or you could use the `RemoveAll` method if you're dealing with a `List<T>`. http://msdn.microsoft.com/en-us/library/wdka673a.aspx
LukeH
A: 

A list or dictionary that could handle any change while you are iterating over it, and figure out which items should be iterated or not, would be very complicated. It's better to solve that for each situation, as most situations are much simpler than the worst imaginable.

You can for example do like this:

someList.Where(foo => foo.Bar).ToList().ForEach(foo => someList.remove(foo));

In most cases that is more efficent than first copying the entire list, as the removed items usually are considerably fewer.

Guffa
+2  A: 

How about some fun with LINQ. Doesn't change behavior of List but makes writing your code nicer. Of course this only works on List not IList but still cool.

someList.RemoveAll(Foo => Foo.Bar == true);
Foovanadil
A nitpick: `RemoveAll` has nothing to do with LINQ. It's just an instance method on the `List<T>` class itself.
LukeH
Yeah, Fair enough.
Foovanadil
+1 - good answer, and I need to try to use that syntax more often. In this case, though, my someList.remove() method is actually several method calls below my iteration, so I can't do anything quite that simple.
Sbodd
A: 

What you're looking for is called a robust iterator. The iterator pattern is implemented in .NET via IEnumerable<T> and IEnumerator<T>.

By default the iterators that you get with the Base Class Library are not robust. But, you can create your own. It will be a little tricky to implement because you have to make sure that you don't go over the same element twice, and you don't skip any elements. But it is certainly possible.

If you create a custom class that derives from List<T>, you can override the GetEnumerator() method to return the robust iterator and therefore use the foreach syntax.

Matt Brunell
A: 

In VB.net there is an class called Collection which implements a VB6-style collection. It's rather goofy in some ways (it's always a collection(Of String, Object)) but its enumerator can be used as you describe, and its performance is reasonably fast. It uses non-case-sensitive string comparisons, and I wish Microsoft had made a generic version, but its enumeration behavior might fit your needs and I think it should be possible to use it in C# if you import the right namespace.

supercat