Question
Collection Was Modified During Enumeration in C#: Why It Happens and How to Fix It
Question
In a C# WCF service hosted in a Windows service, I am getting this error:
Collection was modified; enumeration operation may not execute
The issue seems inconsistent because it often does not happen while a debugger is attached.
NotifySubscribers() is called whenever a data event occurs. Clients subscribe and unsubscribe by being added to or removed from a shared dictionary. The exception appears when, or shortly after, a client unsubscribes. It looks like the next call to NotifySubscribers() fails inside the foreach loop.
Here is the code:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class SubscriptionServer : ISubscriptionServer
{
private static IDictionary<Guid, Subscriber> subscribers;
public SubscriptionServer()
{
subscribers = new Dictionary<Guid, Subscriber>();
}
public void NotifySubscribers(DataRecord sr)
{
foreach (Subscriber s in subscribers.Values)
{
try
{
s.Callback.SignalData(sr);
}
catch (Exception e)
{
DCS.WriteToApplicationLog(
e.Message,
System.Diagnostics.EventLogEntryType.Error);
UnsubscribeEvent(s.ClientId);
}
}
}
public Guid SubscribeEvent(string clientDescription)
{
Subscriber subscriber = new Subscriber();
subscriber.Callback = OperationContext.Current
.GetCallbackChannel<IDCSCallback>();
subscribers.Add(subscriber.ClientId, subscriber);
return subscriber.ClientId;
}
public void UnsubscribeEvent(Guid clientId)
{
try
{
subscribers.Remove(clientId);
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine(
"Unsubscribe Error " + e.Message);
}
}
}
Is the problem that the dictionary is being modified while it is being iterated? Do I need to make the dictionary thread-safe, or is there also an issue with removing items inside the foreach loop itself?
Short Answer
By the end of this page, you will understand why C# throws Collection was modified; enumeration operation may not execute, why it commonly appears with foreach and Dictionary<TKey, TValue>, and how to fix it safely. You will also learn the difference between modifying a collection in the same loop and modifying it from another thread, which is especially important in server code such as WCF services.
Concept
In C#, a foreach loop uses an enumerator behind the scenes. That enumerator expects the collection to stay structurally unchanged while it is being read.
A structural change means operations like:
AddRemoveClear
When a collection changes during enumeration, the enumerator becomes invalid. To protect you from unpredictable behavior, .NET throws this exception:
Collection was modified; enumeration operation may not execute
In your code, this can happen in two different ways:
-
You remove an item during the
foreachloop- Inside
NotifySubscribers(), ifSignalData(sr)throws, you callUnsubscribeEvent(s.ClientId). - That removes an entry from
subscriberswhileforeach (Subscriber s in subscribers.Values)is still iterating.
- Inside
-
Another thread modifies the dictionary at the same time
- A client may subscribe or unsubscribe while is looping.
Mental Model
Think of foreach like reading names from a printed attendance sheet.
While you are moving down the list, someone else should not:
- erase a name,
- add a new name,
- reorder the list.
If that happens, your place on the sheet may no longer make sense.
A Dictionary enumerator works the same way. It expects the collection to remain stable until the loop finishes.
If you need to remove people while processing them, use one of these approaches:
- make a copy of the list first, then iterate the copy,
- collect items to remove in a separate list, then remove them after the loop,
- use a thread-safe collection when multiple threads are involved.
A useful mental shortcut:
- Read while looping: safe
- Change while looping: dangerous unless you control how it happens
Syntax and Examples
The core rule is simple: do not structurally modify a collection while iterating it with foreach.
Problematic pattern
var numbers = new List<int> { 1, 2, 3, 4 };
foreach (var n in numbers)
{
if (n % 2 == 0)
{
numbers.Remove(n); // Throws at runtime
}
}
The loop is reading numbers, but Remove changes the collection.
Safer pattern: remove after enumeration
var numbers = new List<int> { 1, 2, 3, 4 };
var toRemove = new List<int>();
foreach (var n in numbers)
{
if (n % 2 == 0)
{
toRemove.Add(n);
}
}
( n toRemove)
{
numbers.Remove(n);
}
Step by Step Execution
Consider this smaller example:
var words = new List<string> { "A", "B", "C" };
foreach (var word in words)
{
Console.WriteLine(word);
if (word == "B")
{
words.Remove("B");
}
}
What happens step by step
foreachcreates an enumerator forwords.- The enumerator starts with the collection in a valid state.
- First iteration:
wordis"A"Console.WriteLine("A")
- Second iteration:
wordis"B"Console.WriteLine("B")
- Inside the
if,words.Remove("B")changes the list structure. - The enumerator is now invalid because the collection it was tracking has changed.
Real World Use Cases
This issue appears often in real applications whenever code keeps a shared in-memory collection.
Subscriber or client registries
Like your WCF example:
- connected clients
- active WebSocket sessions
- SignalR connections
- event listeners
Job and task processing
A server may loop through active jobs while another thread adds or removes jobs.
Caches and lookup tables
A dictionary may store:
- user sessions
- configuration values
- object instances
- API tokens
If one part of the app reads while another writes, enumeration errors can occur.
UI applications
A WinForms or WPF app may loop through controls or models while an event handler removes one.
Data pipelines
A script may process a collection of files, records, or messages while filtering or deleting them at the wrong time.
The general lesson: if a collection is shared, think carefully about when it is read and when it is changed.
Real Codebase Usage
In production code, developers usually solve this with a small set of common patterns.
1. Snapshot before iterating
Very common when broadcasting to clients:
Subscriber[] snapshot;
lock (_sync)
{
snapshot = subscribers.Values.ToArray();
}
This reduces the time spent holding a lock and makes the iteration stable.
2. Remove failed items after processing
Instead of removing during enumeration, collect IDs first:
var failedIds = new List<Guid>();
foreach (var s in snapshot)
{
try
{
s.Callback.SignalData(sr);
}
catch
{
failedIds.Add(s.ClientId);
}
}
lock (_sync)
{
foreach (var id in failedIds)
{
subscribers.Remove(id);
}
}
This pattern is clean and easy to reason about.
3. Guard shared state with lock
For ordinary dictionaries, a private lock object is standard:
private static readonly object _sync = new object();
Common Mistakes
1. Removing from the same collection inside foreach
Broken example:
foreach (var item in items)
{
if (ShouldRemove(item))
{
items.Remove(item);
}
}
Why it fails:
foreachexpects the collection to stay unchanged.
How to avoid it:
- use a snapshot
- collect items to remove later
- use a loop structure that supports safe indexed removal when appropriate
2. Assuming the debugger proves the code is safe
Beginners often think:
- “It works in debug mode, so the logic must be fine.”
But concurrency bugs depend on timing. The debugger changes timing, so the bug may hide.
How to avoid it:
- trust the exception and review shared-state access
- test without breakpoints
- look for race conditions in server code
3. Using Dictionary from multiple threads without synchronization
Broken example:
private static Dictionary<Guid, Subscriber> subscribers = new();
()
{
subscribers.Add(s.ClientId, s);
}
{
( sub subscribers.Values)
{
sub.Callback.SignalData(sr);
}
}
Comparisons
| Approach | Prevents modifying during foreach | Handles multithreading | Good for your scenario | Notes |
|---|---|---|---|---|
Remove inside foreach | No | No | No | Causes the exception |
| Collect items, remove later | Yes | Not by itself | Yes, with locking | Simple and clear |
Iterate over ToList() / ToArray() snapshot | Yes | Not by itself | Yes, with locking | Great for broadcasting |
lock around dictionary access | Yes, if used correctly |
Cheat Sheet
// Unsafe: modifying a collection during foreach
foreach (var item in collection)
{
collection.Remove(item); // don't do this
}
// Safe pattern: snapshot + lock
private static readonly Dictionary<Guid, Subscriber> subscribers = new();
private static readonly object _sync = new object();
Subscriber[] snapshot;
lock (_sync)
{
snapshot = subscribers.Values.ToArray();
}
foreach (var s in snapshot)
{
// work with s
}
// Safe pattern: collect removals, then remove after loop
var failedIds = new List<Guid>();
foreach (var s in snapshot)
{
try
{
s.Callback.SignalData(sr);
}
catch
{
failedIds.Add(s.ClientId);
}
}
lock (_sync)
{
foreach (var id in failedIds)
{
subscribers.Remove(id);
}
}
Rules to remember
FAQ
Why does foreach throw when a collection changes?
Because foreach uses an enumerator that expects the collection structure to remain unchanged while iterating.
Is removing the current item inside foreach ever safe in C#?
Not for normal collections like List<T> or Dictionary<TKey, TValue>. Use a snapshot, a separate removal list, or a different loop strategy.
Does this mean Dictionary is broken?
No. Dictionary<TKey, TValue> works correctly, but it is not designed for concurrent modification during enumeration.
Why does the error disappear when debugging?
Because the debugger changes execution timing. Race conditions often become less frequent or move to different places when code runs more slowly.
Should I use ConcurrentDictionary here?
Maybe, but not automatically. For a subscriber list, a lock plus snapshot iteration is often simpler and easier to reason about.
Should I lock the whole NotifySubscribers() method?
Usually no. Avoid holding a lock while calling client callbacks, because callbacks may be slow or re-entrant. Copy first, then notify outside the lock.
Why is initializing a static dictionary in the constructor a problem?
Mini Project
Description
Build a small in-memory notification service that keeps track of subscribers and sends a message to each one. The project demonstrates how to safely iterate over a shared collection while removing failed subscribers. This mirrors real server-side patterns such as event broadcasting, connection tracking, and cleanup of disconnected clients.
Goal
Create a C# notification broadcaster that safely sends messages to all subscribers without throwing collection-modified exceptions.
Requirements
- Store subscribers in a dictionary keyed by
Guid. - Allow subscribers to be added and removed.
- Notify all current subscribers with a broadcast method.
- If a subscriber fails during notification, remove it safely.
- Prevent collection-modified errors during iteration.
Keep learning
Related questions
AddTransient vs AddScoped vs AddSingleton in ASP.NET Core Dependency Injection
Learn the differences between AddTransient, AddScoped, and AddSingleton in ASP.NET Core DI with examples and practical usage.
Best Way to Repeat a Character in C#: Building Repeated Strings Efficiently
Learn the best way to repeat a character in C#, compare StringBuilder, string concatenation, and simpler built-in options.
C# Array Initialization Syntaxes Explained
Learn all common C# array initialization syntaxes with examples, rules, comparisons, and mistakes beginners often make.