dotnet/wpf · error · InvalidOperationException

SR.Enumerator_CollectionChanged

Error message

SR.Enumerator_CollectionChanged

What it means

Model3DCollection's strongly-typed enumerator throws InvalidOperationException(SR.Enumerator_CollectionChanged) from MoveNext when the collection's version counter changed since enumeration started. WPF enumerators are fail-fast: any Add/Remove/Insert during a foreach invalidates the enumerator. The _index == -2 sentinel indicates past-the-end state handled separately; this throw is the mid-enumeration mutation case.

Solutions

  1. Snapshot before iterating: foreach (var m in collection.ToArray()) so mutation of the original is safe.
  2. Collect items to remove/add in a temporary list, then apply the changes after the loop.
  3. If multithreaded, marshal all collection mutations to the UI (dispatcher) thread and never enumerate while another thread mutates.

Example fix

// before
foreach (var m in collection)
{
    if (ShouldRemove(m)) collection.Remove(m); // InvalidOperationException
}
// after
foreach (var m in collection.ToArray())
{
    if (ShouldRemove(m)) collection.Remove(m);
}
Defensive patterns

Strategy: try-catch

Validate before calling

int v0 = collection.Count; // capture; if you cannot guarantee no mutation, snapshot
var snapshot = collection.ToArray();

Try / catch

try { foreach (var m in collection) { /* ... */ } } catch (InvalidOperationException ex) when (ex.Message.Contains("CollectionChanged")) { // retry with a snapshot
    foreach (var m in collection.ToArray()) { /* ... */ } }

Prevention

When it happens

Trigger: Modifying the Model3DCollection (Add, Remove, Insert, Clear, indexer set) between MoveNext calls inside a foreach or while advancing the enumerator manually.

Common situations: Removing a model from the collection in response to a property change triggered while iterating; UI-event handlers mutating the collection during enumeration; background threads touching the collection while the UI thread enumerates.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Media3D/Generated/Model3DCollection.cs:830

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