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
- Snapshot the collection before iterating: iterate over a copied array (e.g. visual3DCollection.ToArray()) when mutation is expected.
- Restructure the loop so Add/Remove happens after enumeration completes, e.g. collect items to remove first, then apply.
- 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
- Never call Add/Remove/Clear on a Visual3DCollection inside its own foreach; collect changes and apply afterwards.
- Snapshot with ToArray() when mutation is expected during iteration.
- Keep all Visual3D collection mutations on the UI thread.
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
- InvalidOperationException
- SR.Enumerator_CollectionChanged
- SR.Enumerator_CollectionChanged
- SR.Enumerator_CollectionChanged
- SR.Enumerator_CollectionChanged
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)