views:

188

answers:

3

I don't know why I have an IndexOutOfRangeException when I am clearing a System.Collections.Generic.List<T>. Does this make sense?

List<MyObject> listOfMyObject = new List<MyObject>();
listOfMyObject.Clear();
+7  A: 

Are you sure that that code throws an exception? I have

using System.Collections.Generic;

class MyObject { }

class Program {
    static void Main(string[] args) {
        List<MyObject> listOfMyObject = new List<MyObject>();
        listOfMyObject.Clear();
    }
}

and I do not get an exception.

Is your real-life example more complex? Perhaps you have multiple threads simultaneously accessing the list? Can we see a stack trace?

List<T>.Clear is really quite simple. Using Reflector:

public void Clear() {
    if (this._size > 0) {
        Array.Clear(this._items, 0, this._size);
        this._size = 0;
    }
    this._version++;
}

In the case when the list already empty, that is not going to ever throw an exception. However, if you are modifying the list on another thread, Array.Clear could throw an IndexOutOfRangeException exception. So if another thread removes an item from the list then this._size (the number of items to clear) will be too big.

Jason
+1  A: 

The documentation doesn't mention any Exception this method throws, your problem is probably elsewhere.
List<T>.Clear

Kobi
But Array.Clear can throw an `IndexOutOfRangeException`. As `List<T>.Clear` depends on `Array.Clear`, `List<T>.Clear` could trigger an `IndexOutOfRangeException` in `Array.Clear` in a multi-threaded scenario.
Jason
Well, that's good to know. The documentation does say the standard `instance members are not guaranteed to be thread safe`. It's odd multi-treading wasn't mentioned in the question (or is wpf close enough?)
Kobi
Kobi,you're right, it's working with multi-threading though.
paradisonoir
+14  A: 

This typically happens if multiple threads are accessing the list simultaneously. If one thread deletes an element while another calls Clear(), this exception can occur.

The "answer" in this case is to synchronize this appropriately, locking around all of your List access.


Edit:

In order to handle this, the simplest method is to encapsulate your list within a custom class, and expose the methods you need, but lock as needed. You'll need to add locking to anything that alters the collection.

This would be a simple option:

public class MyClassCollection
{
    // Private object for locking
    private readonly object syncObject = new object(); 

    private readonly List<MyObject> list = new List<MyObject>();
    public this[int index]
    {
        get { return list[index]; }
        set
        {
             lock(syncObject) { 
                 list[index] = value; 
             }
        }
    }

    public void Add(MyObject value)
    {
         lock(syncObject) {
             list.Add(value);
         }
    }

    public void Clear()
    {
         lock(syncObject) {
             list.Clear();
         }
    }
    // Do any other methods you need, such as remove, etc.
    // Also, you can make this class implement IList<MyObject> 
    // or IEnumerable<MyObject>, but make sure to lock each 
    // of the methods appropriately, in particular, any method
    // that can change the collection needs locking
}
Reed Copsey
yes I do have simultaneous threads, so how can I lock it? I thought maybe implement it in a singleton, but that does not make it thread safe, does it?
paradisonoir
No, the trick is that any time you need to change your list, at all, you'll want to lock around it. I'll edit my answer to show you a basic implementation.
Reed Copsey