Yan Cui
I help clients go faster for less using serverless technologies.
I had a problem with the project I’m working on at work where on the base class I had a list which its child classes need to have access to in a read-only capacity but manipulation to the list itself is handled by the base class only. However, the standard C# List<T> and Enumerator<T> are not thread-safe and we started seeing problems when the list is modified by one thread whilst another is trying to loop through it.
Whilst looking for a clean solution we found this article on CodeProject:
http://www.codeproject.com/KB/cs/safe_enumerable.aspx
The article had covered much of the implementation you need, but left some gaps you need to plug yourself for a thread-safe list which I have included below along with some of the useful methods 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;
}
}
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();
}
}
Whenever you’re ready, here are 3 ways I can help you:
- Production-Ready Serverless: Join 20+ AWS Heroes & Community Builders and 1000+ other students in levelling up your serverless game. This is your one-stop shop for quickly levelling up your serverless skills.
- I help clients launch product ideas, improve their development processes and upskill their teams. If you’d like to work together, then let’s get in touch.
- Join my community on Discord, ask questions, and join the discussion on all things AWS and Serverless.

This is one of the best answer so far, I have read online. Just useful information. Very well presented. Thanks for sharing with us. I had found another nice post with wonderful explanation on Enumeration in c#, which is also helped me to complete my task. For more details of that post check out this link….
http://mindstick.com/Articles/ade257fc-7058-4f60-a0fe-85c7ca52f004/?Enumeration%20in%20c#
Thanks everyone for your precious post.
Just want to point out that your implementation is flawed, try this:
foreach(int value in safeList)
{
safeList.Remove(value);
}
Just would like to share with others that nowadays this approach is not a good idea. You might not even notice the problem in the beginning but if you use async/await inside the foreach enumeration Monitor.Exit might be called on a different from the Monitor.Enter thread which leads to an unpredictable exception.