Thread-safe enumeration in C#

I had a prob­lem with the project I’m work­ing on at work where on the base class I had a list which its child class­es need to have access to in a read-only capac­i­ty but manip­u­la­tion to the list itself is han­dled by the base class only. How­ev­er, the stan­dard C# List<T> and Enumerator<T> are not thread-safe and we start­ed see­ing prob­lems when the list is mod­i­fied by one thread whilst anoth­er is try­ing to loop through it.

Whilst look­ing for a clean solu­tion we found this arti­cle on Code­Pro­ject:

http://www.codeproject.com/KB/cs/safe_enumerable.aspx

The arti­cle had cov­ered much of the imple­men­ta­tion you need, but left some gaps you need to plug your­self for a thread-safe list which I have includ­ed below along with some of the use­ful meth­ods you’d find on List<T>:

Thread-safe Enumerator<T>

/// <summary>
/// A thread-safe IEnumerator implementation.
/// See: http://www.codeproject.com/KB/cs/safe_enumerable.aspx
/// </summary>
public class SafeEnumerator<T>: IEnumerator<T>
{
    // this is the (thread-unsafe)
    // enumerator of the underlying collection
    private readonly IEnumerator<T> _inner;

    // this is the object we shall lock on.
    private readonly object _lock;

    public SafeEnumerator(IEnumerator<T> inner, object @lock)
    {
        _inner = inner;
        _lock = @lock;

        // entering lock in constructor
        Monitor.Enter(_lock);
    }

    public T Current
    {
        get { return _inner.Current; }
    }

    object IEnumerator.Current
    {
        get { return Current; }
    }

    public void Dispose()
    {
        // .. and exiting lock on Dispose()
        // This will be called when foreach loop finishes
        Monitor.Exit(_lock);
    }

    /// <remarks>
    /// we just delegate actual implementation
    /// to the inner enumerator, that actually iterates
    /// over some collection
    /// </remarks>
    public bool MoveNext()
    {
        return _inner.MoveNext();
    }

    public void Reset()
    {
        _inner.Reset();
    }
}

Thread-safe List<T>

/// <summary>
/// A thread-safe IList implementation using the custom SafeEnumerator class
/// See: http://www.codeproject.com/KB/cs/safe_enumerable.aspx
/// </summary>
public class SafeList<T> : IList<T>
{
    // the (thread-unsafe) collection that actually stores everything
    private readonly List<T> _inner;

    // this is the object we shall lock on.
    private readonly object _lock = new object();

    public SafeList()
    {
        _inner = new List<T>();
    }

    public int Count
    {
        get
        {
            lock (_lock)
            {
                return _inner.Count;
            }
        }
    }

    public bool IsReadOnly
    {
        get { return false; }
    }

    public T this[int index]
    {
        get
        {
            lock (_lock)
            {
                return _inner[index];
            }
        }
        set
        {
            lock (_lock)
            {
                _inner[index] = value;
            }
        }
    }

    IEnumerator<T> IEnumerable<T>.GetEnumerator()
    {
        lock (_lock)
        {
            // instead of returning an usafe enumerator,
            // we wrap it into our thread-safe class
            return new SafeEnumerator<T>(_inner.GetEnumerator(), _lock);
        }
    }

    /// <remarks>
    /// To be actually thread-safe, our collection must be locked on all other operations
    /// </remarks>
    public void Add(T item)
    {
        lock (_lock)
        {
            _inner.Add(item);
        }
    }

    public void Clear()
    {
        lock (_lock)
        {
            _inner.Clear();
        }
    }

    public bool Contains(T item)
    {
        lock (_lock)
        {
            return _inner.Contains(item);
        }
    }

    public void CopyTo(T[] array, int arrayIndex)
    {
        lock (_lock)
        {
            _inner.CopyTo(array, arrayIndex);
        }
    }

    public bool Remove(T item)
    {
        lock (_lock)
        {
            return _inner.Remove(item);
        }
    }

    public IEnumerator GetEnumerator()
    {
        lock (_lock)
        {
            return new SafeEnumerator<T>(_inner.GetEnumerator(), _lock);
        }
    }

    public int IndexOf(T item)
    {
        lock (_lock)
        {
            return _inner.IndexOf(item);
        }
    }

    public void Insert(int index, T item)
    {
        lock (_lock)
        {
            _inner.Insert(index, item);
        }
    }

    public void RemoveAt(int index)
    {
        lock (_lock)
        {
            _inner.RemoveAt(index);
        }
    }

    public ReadOnlyCollection<T> AsReadOnly()
    {
        lock (_lock)
        {
            return new ReadOnlyCollectio<T>(this);
        }
    }

    [CheckParameters]
    public void ForEach([NotNull] Action<T> action)
    {
        lock (_lock)
        {
            foreach (var item in _inner)
            {
                action(item);
            }
        }
    }

    [CheckParameters]
    public bool Exists([NotNull] Predicate<T> match)
    {
        lock (_lock)
        {
            foreach (var item in _inner)
            {
                if (match(item))
                {
                    return true;
                }
            }
        }
        return false;
    }
}
You can see I’ve used the Check­Pa­ra­me­ters and Not­Null attrib­ut­es for para­me­ter val­i­da­tion using Post­Sharp, you can eas­i­ly sub­sti­tute them with nor­mal null checks.

Thread-safe IEnumerable<T>

/// <summary>
/// A thread-safe IEnumerable implementation
/// See: http://www.codeproject.com/KB/cs/safe_enumerable.aspx
/// </summary>
public class SafeEnumerable<T> : IEnumerable<T>
{
    private readonly IEnumerable<T> _inner;
    private readonly object _lock;

    public SafeEnumerable(IEnumerable<T> inner, object @lock)
    {
        _lock = @lock;
        _inner = inner;
    }

    public IEnumerator<T> GetEnumerator()
    {
        return new SafeEnumerator<T>(_inner.GetEnumerator(), _lock);
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}