dotnet/wpf · error · InvalidOperationException

SR.Enumerator_CollectionChanged

Error message

SR.Enumerator_CollectionChanged

What it means

Thrown by the collection enumerator's MoveNext when the underlying GeneralTransformCollection was modified after the enumerator was created. The enumerator's version stamp no longer matches the collection's, so iteration is aborted with an InvalidOperationException.

Solutions

  1. Collect items to add/remove into a temporary list and apply them after the foreach completes
  2. Iterate over a snapshot (e.g. collection.ToArray()) when mutation may occur
  3. Use a for loop with index bounds checks if deliberate mutation-while-iterating is required

Example fix

// before
foreach (var t in collection) { if (bad(t)) collection.Remove(t); }
// after
var toRemove = collection.Where(bad).ToArray();
foreach (var t in toRemove) { collection.Remove(t); }
Defensive patterns

Strategy: try-catch

Try / catch

bool done = false;
while (!done) {
  try { foreach (var t in collection.ToArray()) { Process(t); } done = true; }
  catch (InvalidOperationException) { /* retry with fresh snapshot */ }
}

Prevention

When it happens

Trigger: Calling Add, Remove, Insert, Clear, or the indexer setter while a foreach loop is enumerating the collection — including modifications made from event handlers, animation callbacks, or another thread during enumeration.

Common situations: Enumerating a TransformCollection and adding transforms inside the loop, UI-driven handlers mutating the collection during enumeration, or cross-thread access without synchronization.

Related errors


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

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Media/Generated/GeneralTransformCollection.cs:797

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