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
- Collect items to remove/add into a separate list during enumeration, then apply the mutations after the loop.
- Use set.RemoveWhere(predicate) for filter-style mutations, which handles versioning internally.
- 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
- Use RemoveWhere for filter mutations.
- Snapshot ToArray/ToList before mutating during iteration.
- Synchronize cross-fiber access to shared SortedSets.
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
- InvalidOperation_EnumOpCantHappen
- ArgumentOutOfRange_Index
- Arg_ArrayPlusOffTooSmall
- Argument_IncompatibleArrayType
- SortedSet_LowerValueGreaterThanUpperValue
AI-assisted analysis of egametang/ET@5cab01f7a8 (2026-08-13).
Data as JSON: /api/errors/dd217fd4420ef7cc.
Report an issue: GitHub.