dotnet/wpf · error · InvalidOperationException

SR.Enumerator_CollectionChanged

Error message

SR.Enumerator_CollectionChanged

What it means

FreezableCollection<T>.Enumerator.MoveNext throws InvalidOperationException(SR.Enumerator_CollectionChanged) when the collection's version changed since the enumerator was created (_version != _list._version). Enumerators are fail-fast: any Add/Remove/Insert/RemoveAt/Clear between MoveNext calls invalidates the enumerator.

Solutions

  1. Iterate over a snapshot: foreach (var item in collection.ToList()) so mutations affect the copy.
  2. Collect items to remove in a list, then apply Remove/RemoveAt after the loop.
  3. For reverse-index removal loops, iterate from Count-1 down to 0 using the indexer instead of foreach.
  4. Avoid cross-thread mutation during enumeration; synchronize access.

Example fix

// before
foreach (var item in collection)
    if (IsBad(item)) collection.Remove(item); // throws
// after
foreach (var item in collection.ToList())
    if (IsBad(item)) collection.Remove(item);
Defensive patterns

Strategy: type-guard

Validate before calling

var snapshot = collection.ToList(); // version-stable copy
foreach (var item in snapshot) { /* safe to mutate original now */ }

Type guard

static bool IsCurrent(Enumerator e) => !e.IsStale(); // prefer snapshot pattern instead

Try / catch

try { foreach (var item in collection) Process(item); }
catch (InvalidOperationException) { foreach (var item in collection.ToList()) Process(item); }

Prevention

When it happens

Trigger: foreach over a FreezableCollection while modifying it inside the loop body (Add/Remove/Clear), or a CollectionChanged handler mutates the collection during the same enumeration.

Common situations: Removing items while iterating to 'clean up', a change handler triggered mid-foreach that mutates the collection, multi-threaded access where one thread enumerates while another mutates.

Related errors


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

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/FreezableCollection.cs:1022

            {
                _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)