dotnet/wpf · error · InvalidOperationException

SR.Enumerator_CollectionChanged

Error message

SR.Enumerator_CollectionChanged

What it means

The GeneralTransform3DCollection enumerator is version-checked: MoveNext throws InvalidOperationException (Enumerator_CollectionChanged) if the collection's version differs from the version captured when enumeration started. Any add, remove, or structural change during iteration invalidates the enumerator.

Solutions

  1. Iterate a snapshot: foreach (var t in collection.ToArray()) so mutations do not affect the loop.
  2. Collect items to remove in a list, then apply removals after the loop.
  3. Use a reverse for-loop over indices when removing by index.

Example fix

// before
foreach (var t in collection)
    if (ShouldRemove(t)) collection.Remove(t);
// after
foreach (var t in collection.ToArray())
    if (ShouldRemove(t)) collection.Remove(t);
Defensive patterns

Strategy: fallback

Validate before calling

// snapshot before iterating
var snapshot = collection.ToArray();

Try / catch

try
{
    foreach (var t in collection) { /* ... */ }
}
catch (InvalidOperationException ex) when (ex.Message.Contains("changed"))
{
    foreach (var t in collection.ToArray()) { /* retry on snapshot */ }
}

Prevention

When it happens

Trigger: Calling Add/Insert/Remove/RemoveAt/Clear on the GeneralTransform3DCollection inside a foreach loop or between MoveNext calls.

Common situations: Removing transforms while iterating to 'clean up'; adding a transform inside an iteration based on a condition; collection mutated on another thread or via data binding during iteration.

Related errors


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

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Media3D/Generated/GeneralTransform3DCollection.cs:795

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