dotnet/wpf · error · InvalidOperationException

SR.Enumerator_CollectionChanged

Error message

SR.Enumerator_CollectionChanged

What it means

Visual3DCollection's enumerator detects that the underlying collection was modified (its _version changed) after the enumerator was created, and throws InvalidOperationException to prevent undefined iteration behavior. This is the standard fail-fast pattern for WPF collections whose enumerators do not tolerate mutation.

Solutions

  1. Snapshot the collection before iterating: iterate over a copied array (e.g. visual3DCollection.ToArray()) when mutation is expected.
  2. Restructure the loop so Add/Remove happens after enumeration completes, e.g. collect items to remove first, then apply.
  3. Marshal all mutations to the UI thread and avoid mutating while any active enumerator exists; use index-based for loops iterating backwards when removing.

Example fix

// before
foreach (var v in viewport3D.Children)
{
    if (ShouldRemove(v)) viewport3D.Children.Remove(v); // throws
}
// after
foreach (var v in viewport3D.Children.ToArray())
{
    if (ShouldRemove(v)) viewport3D.Children.Remove(v);
}
Defensive patterns

Strategy: validation

Validate before calling

// enumerate a snapshot if mutation is possible
var snapshot = children.ToArray(); // System.Linq

Type guard

static bool CanSafelyEnumerate(System.Windows.Media.Media3D.Visual3DCollection c) => c != null; // enumerators do not expose version state, so guard by snapshotting

Try / catch

try { foreach (var v in children) { /* ... */ } }
catch (InvalidOperationException ex) when (ex.Message.Contains("changed") || ex.Message.Contains("Enumerator"))
{ foreach (var v in children.ToArray()) { /* retry on snapshot */ } }

Prevention

When it happens

Trigger: Calling MoveNext() on a Visual3DCollection enumerator after any Add/Remove/Clear/Insert or other structural change to the collection since enumeration started.

Common situations: Iterating a Viewport3D's Children (Visual3DCollection) in a foreach while adding or removing Visual3D children on the same pass, often inside render/update code or a Loaded handler; or a background thread mutating 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/b06af3e34f162a68. Report an issue: GitHub.

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Media3D/Visual3DCollection.cs:618

                Debug.Assert(list != null, "list may not be null.");

                _list = list;
                _index = -1;
                _version = _list._version;
            }

            #endregion Constructors

            #region Public Methods

            /// <summary>
            ///     Advances the enumerator to the next IElement of the collection.
            /// </summary>
            public bool MoveNext()
            {
                if (_list._version != _version)
                {
                    throw new InvalidOperationException(SR.Enumerator_CollectionChanged);
                }

                int count = _list.Count;

                if (_index < count)
                {
                    _index++;
                }

                return _index < count;
            }

            /// <summary>
            ///     Resets the enumerator to its initial position.
            /// </summary>
            public void Reset()
            {
                if (_list._version != _version)

View on GitHub (pinned to 81131a70a4)