egametang/ET · error · InvalidOperationException

InvalidOperation_EnumFailedVersion

Error message

InvalidOperation_EnumFailedVersion

What it means

Thrown by SortedSet Enumerator.MoveNext when the set's version no longer matches the enumerator's captured _version. SortedSet bumps its version on every structural modification (Add/Remove/Clear), and the enumerator detects the drift to fail fast rather than yield inconsistent data. Surfaces as InvalidOperationException (InvalidOperation_EnumFailedVersion).

Source

Thrown at Packages/cn.etetet.core/Scripts/Core/Share/Collection/SortedSet.cs:2059

                    else if (next == null || !_tree.IsWithinRange(next.Item))
                    {
                        node = other;
                    }
                    else
                    {
                        node = next;
                    }
                }
            }

            public bool MoveNext()
            {
                // Make sure that the underlying subset has not been changed since
                _tree.VersionCheck();

                if (_version != _tree.version)
                {
                    throw new InvalidOperationException(SR.InvalidOperation_EnumFailedVersion);
                }

                if (_stack.Count == 0)
                {
                    _current = null;
                    return false;
                }

                _current = _stack.Pop();
                Node node = (_reverse ? _current.Left : _current.Right);
                Node next, other;
                while (node != null)
                {
                    next = (_reverse ? node.Right : node.Left);
                    other = (_reverse ? node.Left : node.Right);
                    if (_tree.IsWithinRange(node.Item))
                    {
                        _stack.Push(node);

View on GitHub (pinned to 5cab01f7a8)

Solutions

  1. Collect items to remove/add into a separate list during enumeration, then apply the mutations after the loop.
  2. Use set.RemoveWhere(predicate) for filter-style mutations, which handles versioning internally.
  3. Snapshot with set.ToArray()/ToList() before mutating, or lock/synchronize cross-fiber access.

Example fix

// before
foreach (var x in set) { if (pred(x)) set.Remove(x); } // throws

// after
set.RemoveWhere(pred);
Defensive patterns

Strategy: validation

Validate before calling

// Buffer mutations during enumeration.
List<T> toRemove = new List<T>();
foreach (var x in set) if (pred(x)) toRemove.Add(x);
foreach (var x in toRemove) set.Remove(x);

Try / catch

try { foreach (var x in set) { /* ... */ } } catch (InvalidOperationException ex) when (ex.Message.Contains("EnumFailedVersion")) { /* snapshot with ToArray and retry */ }

Prevention

When it happens

Trigger: Modifying the SortedSet (Add/Remove/Clear/GetViewBetween that mutates) inside a foreach over the same set; holding an enumerator across a later mutation then calling MoveNext; concurrent access from another fiber/thread.

Common situations: foreach (var x in set) set.Remove(x); the classic mutate-while-enumerating pattern; nested operations that re-enter the set; multi-threaded ET fiber access without synchronization.

Related errors


AI-assisted analysis of egametang/ET@5cab01f7a8 (2026-08-13). Data as JSON: /api/errors/dd217fd4420ef7cc. Report an issue: GitHub.