dotnet/wpf · error · InvalidOperationException

SR.Enumerator_CollectionChanged

Error message

SR.Enumerator_CollectionChanged

What it means

The VisualCollection Enumerator's MoveNext throws InvalidOperationException when the collection's version changed after the enumerator was created (a visual was added, removed, or moved during enumeration). WPF enumerators detect structural modification via a version stamp and fail fast instead of skipping or repeating items.

Solutions

  1. Snapshot the children before enumerating: iterate over a copied array (e.g. foreach (Visual v in visualCollection.ToArray()))
  2. Collect items to remove in one pass, then remove them after the loop
  3. Do all collection mutations and enumerations on the UI thread and never re-entrantly inside layout callbacks
  4. Use Count + indexer loop guarded appropriately instead of the enumerator

Example fix

// before
foreach (Visual v in visualCollection)
    if (ShouldRemove(v)) visualCollection.Remove(v); // throws
// after
var toRemove = visualCollection.OfType<Visual>().Where(ShouldRemove).ToList();
foreach (var v in toRemove) visualCollection.Remove(v);
Defensive patterns

Strategy: try-catch

Validate before calling

int v0 = collection.Version; // detect modification yourself around the loop

Try / catch

try { foreach (Visual v in collection) { Process(v); } }
catch (InvalidOperationException ex) when (ex.Message == SR.Enumerator_CollectionChanged || ex.Message.Contains("collection")) { // re-enumerate over a snapshot }

Prevention

When it happens

Trigger: Enumerating a VisualCollection (foreach or manual GetEnumerator) while another thread or re-entrant code calls Add/Insert/Remove/Clear/Move on the same collection, invalidating the version.

Common situations: Modifying children inside a loop over visual children; layout/arrange code mutating the tree while a caller iterates; enumerating from a non-UI thread while UI thread updates; iterating and calling RemoveChild in the same pass.

Related errors


AI-assisted analysis of dotnet/wpf@81131a70a4 (2026-09-14). Data as JSON: /api/errors/b237503a703b847d. Report an issue: GitHub.

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Media/VisualCollection.cs:920

                if (_version == _collection.Version)
                {
                    if ((_index > -2) && (_index < (_collection.InternalCount - 1)))
                    {
                        _index++;
                        _currentElement = _collection[_index];
                        return true;
                    }
                    else
                    {
                        _currentElement = null;
                        _index = -2; // -2 <=> reached the end.
                        return false;
                    }
                }
                else
                {
                    throw new InvalidOperationException(SR.Enumerator_CollectionChanged);
                }
            }

            /// <summary>
            /// Gets the current Visual.
            /// </summary>
            object IEnumerator.Current
            {
                get
                {
                    return this.Current;
                }
            }

            /// <summary>
            /// Gets the current Visual.
            /// </summary>
            public Visual Current

View on GitHub (pinned to 81131a70a4)