dotnet/wpf · error · InvalidOperationException

SR.EnumeratorVersionChanged

Error message

SR.EnumeratorVersionChanged

What it means

The JournalEntryStack enumerator snapshots the journal's version when created; VerifyUnchanged (invoked from MoveNext) compares it against the journal's current version. If entries were added, removed, or the journal was navigated between enumerator creation and a MoveNext call, the enumerator is stale and InvalidOperationException (EnumeratorVersionChanged) is thrown, mirroring the classic 'collection was modified' guard.

Solutions

  1. Materialize the entries into a list (ToList) before navigating, or complete the enumeration before triggering navigation.
  2. Defer navigation out of the enumeration loop (Dispatcher.BeginInvoke) so mutation happens after MoveNext finishes.
  3. If deferred enumeration is intended, re-create the enumerator after any journal change instead of continuing to use the stale one.

Example fix

// before
foreach (var entry in journal) // user clicks Back inside loop -> version changes
{
    Log(entry);
    if (shouldGoBack) navService.GoBack();
}
// after
var snapshot = journal.ToList();
foreach (var entry in snapshot) { Log(entry); }
if (shouldGoBack) navService.GoBack();
Defensive patterns

Strategy: validation

Validate before calling

bool journalUnchanged = journal.Version == enumeratorVersionAtCreation; // snapshot Version before enumerating

Type guard

bool CanEnumerate(Journal j, int capturedVersion) => j.Version == capturedVersion;

Try / catch

try { foreach (var e in journal) Process(e); }
catch (InvalidOperationException) { enumerator = journal.GetEnumerator(); /* restart */ }

Prevention

When it happens

Trigger: Enumerating a WPF navigation Journal's entry stack with foreach while navigation occurs (GoBack/GoForward/journal entries added or removed) on the same thread before the enumeration completes.

Common situations: UI code iterating back-stack entries while a NavigationService navigation (e.g. triggered by a button click or event handler inside the loop) mutates the journal; diagnostics/logging code walking the history while the user navigates.

Related errors


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

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/Navigation/JournalEntryStack.cs:138

            }

            _current = null;
            return false;
        }

        public object Current
        {
            get { return _current; }
        }

        /// <summary>
        /// Verifies that the journal has not been changed since this enumerator was created
        /// </summary>
        protected void VerifyUnchanged()
        {
            if (_version != _journal.Version)
            {
                throw new InvalidOperationException(SR.EnumeratorVersionChanged);
            }
        }

        private Journal _journal;
        private int _start;
        private int _delta;
        private int _next;
        private JournalEntry _current;
        private JournalEntryFilter _filter;
        private int _version;
    }

    internal class LimitedJournalEntryStackEnumerable : IEnumerable, INotifyCollectionChanged
    {
        internal LimitedJournalEntryStackEnumerable(IEnumerable ieble)
        {
            _ieble = ieble;
            INotifyCollectionChanged ichildnotify = ieble as INotifyCollectionChanged;

View on GitHub (pinned to 81131a70a4)