dotnet/wpf · error · InvalidOperationException
SR.Enumerator_CollectionChanged
Error message
SR.Enumerator_CollectionChanged
What it means
The PointCollection enumerator is version-checked: it throws InvalidOperationException(Enumerator_CollectionChanged) from MoveNext when the collection's version counter no longer matches the version captured when the enumerator was created. WPF invalidates outstanding enumerators on any modification (Add, Remove, Clear, indexer set) so iteration never observes a collection mutated mid-enumeration.
Solutions
- Snapshot first: foreach over a copied array (points.ToArray()) and mutate the original.
- Collect items to remove into a separate list, then apply the changes after the loop.
- Use a for loop over the index (counting backwards for removals) instead of foreach.
- Synchronize threads so no mutation happens while enumerating.
Example fix
// before
foreach (Point p in points)
if (p.X < 0) points.Remove(p); // InvalidOperationException
// after
foreach (Point p in points.ToArray())
if (p.X < 0) points.Remove(p); Defensive patterns
Strategy: validation
Validate before calling
long versionBefore = points.GetType().GetProperty("Version", BindingFlags.NonPublic|BindingFlags.Instance)?.GetValue(points) as long? ?? 0; // or simply: iterate over a snapshot Type guard
null
Try / catch
try { foreach (Point p in points) { /* ... */ } }
catch (InvalidOperationException ex) when (ex.Message.Contains("CollectionChanged")) { /* restart iteration over a fresh snapshot */ } Prevention
- Never Add/Remove/Clear a collection while enumerating it; enumerate points.ToArray() instead.
- Collect mutations in a side list and apply them after the loop.
- Avoid cross-thread mutation of UI-thread Freezable collections; use Dispatcher marshaling.
When it happens
Trigger: Calling MoveNext on an Enumerator after any structural change to the underlying PointCollection — e.g. points.Add(...) or points.RemoveAt(i) inside a foreach over points, or on another thread.
Common situations: Modifying a collection inside foreach and removing items; deferred logic that mutates the collection between obtaining the enumerator and iterating; UI/data updates on a different thread while an enumeration is in progress.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- SR.Enumerator_CollectionChanged
- SR.Enumerator_CollectionChanged
- SR.Enumerator_CollectionChanged
- SR.Enumerator_CollectionChanged
- InvalidOperationException
AI-assisted analysis of dotnet/wpf@81131a70a4 (2026-09-14).
Data as JSON: /api/errors/8d4293fc9e2a52c7.
Report an issue: GitHub.
Appendix: source
Thrown at src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Media/Generated/PointCollection.cs:817
{
_list.ReadPreamble();
if (_version == _list._version)
{
if (_index > -2 && _index < _list._collection.Count - 1)
{
_current = _list._collection[++_index];
return true;
}
else
{
_index = -2; // -2 indicates "past the end"
return false;
}
}
else
{
throw new InvalidOperationException(SR.Enumerator_CollectionChanged);
}
}
/// <summary>
/// Sets the enumerator to its initial position, which is before the
/// first element in the collection.
/// </summary>
public void Reset()
{
_list.ReadPreamble();
if (_version == _list._version)
{
_index = -1;
}
else
{
throw new InvalidOperationException(SR.Enumerator_CollectionChanged);View on GitHub (pinned to 81131a70a4)