dotnet/wpf · error · InvalidOperationException

SR.EnumeratorVersionChanged

Error message

SR.EnumeratorVersionChanged

What it means

The view's enumerator (the IEnumerator returned for a CollectionView) throws InvalidOperationException (SR.EnumeratorVersionChanged) when MoveNext (or Reset) is called after the view was refreshed — the view's Timestamp changed, invalidating outstanding enumerators. This prevents enumerating over a view whose contents were rebuilt mid-iteration.

Solutions

  1. Materialize the view into a snapshot (ToList/ToArray) before long-running or re-entrant iteration
  2. Do not Refresh the view while an enumerator over it is still active
  3. Re-obtain the enumerator after each Refresh instead of caching it
  4. Use a using (DeferRefresh) around operations if the enumeration must complete first (then refresh after)

Example fix

// before
var e = view.GetEnumerator();
view.Refresh();
e.MoveNext(); // InvalidOperationException: version changed
// after
var snapshot = view.Cast<object>().ToList();
view.Refresh();
foreach (var item in snapshot) { }
Defensive patterns

Strategy: validation

Validate before calling

var snapshot = view.Cast<object>().ToArray(); // stable across Refresh
foreach (var item in snapshot) { /* safe */ }

Try / catch

try { e.MoveNext(); }
catch (InvalidOperationException) { e = view.GetEnumerator(); /* retry with fresh enumerator */ }

Prevention

When it happens

Trigger: Holding an IEnumerator from a CollectionView (e.g. from foreach with a yield, or stored enumerator) across a call to Refresh(), Filter/SortDescriptions change, or any RefreshInternal; the next MoveNext detects the timestamp mismatch.

Common situations: Async code iterating a view while the UI refreshes it; storing enumerators in fields; modifying Filter inside a loop that is enumerating the same view; LINQ deferred evaluation over a view that gets refreshed between iterations.

Related errors


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

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Data/CollectionView.cs:1630

        #region Internal Types

        internal class PlaceholderAwareEnumerator : IEnumerator
        {
            private enum Position { BeforePlaceholder, OnPlaceholder, OnNewItem, AfterPlaceholder}

            public PlaceholderAwareEnumerator(CollectionView collectionView, IEnumerator baseEnumerator, NewItemPlaceholderPosition placeholderPosition, object newItem)
            {
                _collectionView = collectionView;
                _timestamp = collectionView.Timestamp;
                _baseEnumerator = baseEnumerator;
                _placeholderPosition = placeholderPosition;
                _newItem = newItem;
            }

            public bool MoveNext()
            {
                if (_timestamp != _collectionView.Timestamp)
                    throw new InvalidOperationException(SR.EnumeratorVersionChanged);

                switch (_position)
                {
                    case Position.BeforePlaceholder:
                        // AtBeginning - move to the placeholder
                        if (_placeholderPosition == NewItemPlaceholderPosition.AtBeginning)
                        {
                            _position = Position.OnPlaceholder;
                        }
                        // None or AtEnd - advance base, skipping the new item
                        else if (_baseEnumerator.MoveNext() &&
                                    (_newItem == NoNewItem || _baseEnumerator.Current != _newItem
                                            || _baseEnumerator.MoveNext()))
                        {
                        }
                        // if base has reached the end, move to new item or placeholder
                        else if (_newItem != NoNewItem)
                        {

View on GitHub (pinned to 81131a70a4)