dotnet/wpf · error · InvalidOperationException

SR.Enumerator_CollectionChanged

Error message

SR.Enumerator_CollectionChanged

What it means

The DoubleCollection enumerator's MoveNext validates that the collection's version stamp matches the version captured when enumeration began. If the collection was modified (Add/Remove/Insert/Clear) after the enumerator was created, it throws InvalidOperationException with SR.Enumerator_CollectionChanged. This protects callers from undefined iteration over a mutated list.

Solutions

  1. Materialize the collection first (e.g. foreach over coll.ToArray() or ToList()) when you must modify during iteration.
  2. Collect items to remove in a separate list, then apply removals after the loop ends.
  3. Guard concurrent access with a lock so no mutation occurs while an enumerator is active.

Example fix

// before
foreach (double d in coll) { if (d < 0) coll.Remove(d); } // InvalidOperationException
// after
foreach (double d in coll.ToArray()) { if (d < 0) coll.Remove(d); }
Defensive patterns

Strategy: validation

Validate before calling

// snapshot before iterating to survive mutations
var snapshot = coll.ToArray();

Try / catch

try
{
    foreach (double d in snapshot) { /* mutate coll freely */ }
}
catch (InvalidOperationException ex) when (ex.Message.Contains("Enumerator_CollectionChanged"))
{ /* re-enumerate over a fresh snapshot */ }

Prevention

When it happens

Trigger: Creating an enumerator (foreach over a DoubleCollection), then calling Add/Remove/Insert/Clear on the collection, then continuing MoveNext — the next MoveNext call after the change throws.

Common situations: Modifying a DoubleCollection inside a foreach loop over it (e.g. removing points while iterating animation keyframes); a callback or event handler mutating the collection during enumeration; multi-threaded updates without synchronization.

Related errors


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

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Media/Generated/DoubleCollection.cs:815

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