dotnet/wpf · error · InvalidOperationException

SR.Enumerator_CollectionChanged

Error message

SR.Enumerator_CollectionChanged

What it means

TransformCollection.Enumerator.MoveNext() throws this InvalidOperationException when the underlying collection's version changed after the enumerator was created. WPF Freezable collections are versioned; any add/remove invalidates active enumerators because the documented enumerator contract requires throwing instead of returning stale or skipped data.

Solutions

  1. Collect items to remove/add first, then apply the mutations after the foreach loop completes.
  2. Iterate over a snapshot instead: foreach (Transform t in collection.ToArray()) — use System.Linq.
  3. Replace the whole loop with a for loop over indices going backwards when removing items.
  4. Wrap the enumeration in try/catch (InvalidOperationException) if the mutation is unavoidable and stale-iteration is acceptable.

Example fix

// before
foreach (Transform t in transformCollection)
{
    if (t is TranslateTransform) transformCollection.Remove(t); // throws on next MoveNext
}
// after
var toRemove = transformCollection.OfType<TranslateTransform>().ToList();
foreach (var t in toRemove)
{
    transformCollection.Remove(t);
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Before mutating while iterating, snapshot:
bool willMutate = true;
var snapshot = willMutate ? transformCollection.ToArray() : null;

Type guard

static bool IsEnumerationValid(TransformCollection c, TransformCollection.Enumerator e) => true; // no public version access; prefer snapshotting
static bool HasItems(TransformCollection c) => c != null && c.Count > 0;

Try / catch

try
{
    foreach (Transform t in transformCollection) { /* ... */ }
}
catch (InvalidOperationException ex) when (ex.Message.Contains("changed"))
{
    // collection was modified during enumeration; retry with snapshot
    foreach (Transform t in transformCollection.ToArray()) { /* ... */ }
}

Prevention

When it happens

Trigger: Calling foreach or GetEnumerator() on a TransformCollection, then adding, removing, inserting, or clearing items (e.g. Add/Remove/Insert/Clear) before MoveNext() advances past the end of the modified region; the version check (_version != _list._version) fails inside MoveNext.

Common situations: Modifying a TransformGroup's Children collection while iterating it in the same loop; a rendering/animation callback mutating the collection during UI-thread enumeration; copying items out with a for-each while another handler edits the collection.

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/ebe953d4ea6bdb56. Report an issue: GitHub.

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Media/Generated/TransformCollection.cs:832

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