dotnet/wpf · error · InvalidOperationException

throw new…

Error message

throw new InvalidOperationException(SR.EnumeratorVersionChanged);

What it means

CompositeCollectionView's inner enumerator caches the collection version at creation. CheckVersion() compares the cached version against the collection's current _version, which is bumped on every collection change (Refresh, add/remove, etc.). If the collection was modified after enumeration started, the enumerator is stale and this InvalidOperationException is thrown.

Solutions

  1. Finish enumerating before modifying the collection, or collect items into a list first and iterate the snapshot.
  2. Re-acquire a fresh enumerator (call GetEnumerator again) after modifying the collection.
  3. If cross-thread modification is involved, marshal all collection changes to the thread that owns the collection (Dispatcher.BeginInvoke).
  4. Catch InvalidOperationException and restart iteration with a new enumerator if a stale-enumeration retry is acceptable.

Example fix

// before
foreach (var item in compositeCollection) { if (cond) compositeCollection.Remove(item); }
// after
var snapshot = compositeCollection.Cast<object>().ToList();
foreach (var item in snapshot) { if (cond) compositeCollection.Remove(item); }
Defensive patterns

Strategy: try-catch

Try / catch

// snapshot before mutating
var items = compositeCollection.Cast<object>().ToList();
foreach (var item in items) { /* safe iteration */ }

Prevention

When it happens

Trigger: Calling MoveNext() or MoveNextOrDefault() on a CompositeCollection's enumerator (via IEnumerator) after the CompositeCollection or its underlying views/collections raised a change that invalidated the view (_isInvalidated set or _version changed).

Common situations: Iterating a CompositeCollection bound to UI in a foreach loop while the collection is modified on another thread, in a CollectionChanged handler, or via data binding refresh; holding an enumerator across a Refresh() call.

Related errors


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

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/Data/CompositeCollectionView.cs:1593

            public void Dispose()
            {
                DisposeContainerEnumerator();
            }

            private void DisposeContainerEnumerator()
            {
                IDisposable d = _containerEnumerator as IDisposable;
                d?.Dispose();

                _containerEnumerator = null;
            }

            private void CheckVersion()
            {
                // note: there's a very unlikely possibility that the
                // version number wraps around back to the same number
                if (_isInvalidated || (_isInvalidated = (_version != _view._version)))
                    throw new InvalidOperationException(SR.EnumeratorVersionChanged);
            }

            private CompositeCollection _collection;
            private CompositeCollectionView _view;
            private int _index;
            private object _current;
            private IEnumerator _containerEnumerator;
            private bool _done;
            private bool _isInvalidated = false;
            private int _version;
        }

        #endregion Private Types


        //------------------------------------------------------
        //
        //  Private Fields

View on GitHub (pinned to 81131a70a4)