views:

193

answers:

5

Hi,

My question is is it safe for enumerator to remove item from SortedList?

SortedList<decimal, string> myDictionary;
// omitted code

IEnumerator<decimal, string> enum = myDictionary.GetEnumerator();

while(enum.MoveNext)
{
  // is it ok to remove here?
  myDictionary.Remove(enum.Current.Key);
}
+7  A: 

This will throw an exception - you cannot modify a collection while iterating over it.

If you think about it a little, you will understand why. If adding or removing from the collection was allowed, you would no longer be iterating over the same collection - you either have too many (adding) or not enough items (removing).

Oded
Bonus points if you can explain why this should not be allowed.
NickLarsen
Here you go: "The index sequence is based on the sort sequence. When an element is added, it is inserted into SortedList in the correct sort order, and the indexing adjusts accordingly. When an element is removed, the indexing also adjusts accordingly. Therefore, the index of a specific key/value pair might change as elements are added or removed from the SortedList object." MSDN, Linked List documentation.
Piotr Justyna
I was thinking about STL iterators where some of them were safe to delete and some were not.
Captain Comic
Fact that you can smash your fingers with a hammer doesn't mean, that you have to do it :)
Piotr Justyna
+2  A: 

Operations on the list during iterations are not supported in general. The expected behaviour is to throw an exception, but even if a collection fails to do this you must not rely on this working correctly.

You can first copy the elements into another list and then iterate over this new list of items to be modified.

Lucero
+2  A: 

No. An InvalidOperationExcpetion is thrown. I agree that already enumerated items might be deletable since there is a fixed index. However the issue is the following:

The implementation of SortedList is not clever enough to figure out that the removal will have no affect on the further execution of the enumerable. And to keep it simple and performing well, it shouldn't.

ntziolis
+3  A: 

As already stated what you are looking to do is not possible. However, an alternative solution would be to simply maintain a list of items marked for deletion and then remove these afterwords. I would also opt for a foreach rather than a while loop, less code e.g.

var removeList = new List<decimal>();
foreach (var item in myDictionary)
{
    // have a condition which indicates which items are to be removed
    if (item.Key > 1)
    {
        removeList.Add(item.Key);
    }
}

Or if you are simply trying to retrieve items for deletion, use LINQ

var removeList = myDictionary.Where(pair => pair.Key > 1).Select(k => k.Key).ToList();

Then just remove them from the list.

// remove from the main collection
foreach (var key in removeList)
{
    myDictionary.Remove(key);
}
James
Are you sure your second example will work? I didn't try it out, but your `removeList` is just a query and will also iterate *on the fly* over the list, leading to the same exception as before. I think you need to add a `.ToList()` or `.ToArray()` to get a new one, so that you are able to change your original list.
Oliver
@Oliver: +1 good spot, I never tested myself either. Will update
James
+1  A: 

As others have already pointed out it will not work. However, since the collection is a SortedList you can use the RemoveAt method.

This method has a slightly better memory profile since it requires no overhead as opposed to a O(n) increase using a separate list to keep track of removals. It would also have a O(n^2) performance profile as opposed to O(n^2 * log(n)). The RemoveAt method is O(n) since it must perform an array copy. The Remove method adds a O(log(n)) operation to find the index before internally calling RemoveAt. All of this is probably of no concern to you, but it is useful know in case you run into situations involving a lot of 'n'.

var myDictionary = new SortedList<decimal, string>();

// omitted code

int i = 0;
while (myDictionary.Count > 0 && i < myDictionary.Count)
{
  if (/* predicate to use for removal */)
  {
    myDictionary.RemoveAt(i);
  }
  else
  {
    i++;
  }
}
Brian Gideon