Are there good IList and IDictionary implementations in C# that support fail-safe iteration?
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 penalti开发者_JS百科es associated with using those lists.) Is there an equivalent in C# that I'm just not aware of?
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.
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);
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);
}
}
}
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.
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.
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.
If you only want to remove stuff use the mentioned RemoveAll-method. If you also want to do stuff with some of the elements you can do this:
for(int i = 0; i < list.Count; i++) {
var item = list[i];
if(item.Foo) {
list.RemoveAt(i--);
continue;
}
item.Bar();
}
精彩评论