C#中使用UniRx移除观测列表元素时避免异常的方法
解决UniRx遍历集合时修改引发的InvalidOperationException问题
错误原因
你遇到的InvalidOperationException是因为SelectMany(_ => _gameState.enemies)直接对原集合进行枚举,而在Subscribe回调中修改了原集合(调用Remove)——.NET的集合枚举器不允许在枚举过程中修改集合结构,因此触发了这个异常。
无内存分配的替代方案
最直接的无分配方案是反向遍历原集合,结合UniRx的EveryUpdate驱动操作:
Observable.EveryUpdate() .Subscribe(_ => { // 反向遍历,避免移除元素导致的索引错乱 for (int i = _gameState.enemies.Count - 1; i >= 0; i--) { var enemy = _gameState.enemies[i]; if (enemy.x > 5) { _gameState.enemies.RemoveAt(i); } } })
这种方式不需要创建任何新集合,完全在原集合上操作,没有额外内存分配,同时反向遍历不会因为移除元素导致后续元素索引偏移,保证遍历的正确性。
如果希望保留Observable链式风格,还可以复用一个预初始化的临时列表来避免重复分配:
// 提前初始化临时列表,仅分配一次内存 private readonly List<Enemy> _tempRemovalList = new List<Enemy>(); // Observable逻辑 Observable.EveryUpdate() .Subscribe(_ => { _tempRemovalList.Clear(); // 先收集需要移除的元素 foreach (var enemy in _gameState.enemies) { if (enemy.x > 5) { _tempRemovalList.Add(enemy); } } // 批量移除 foreach (var enemy in _tempRemovalList) { _gameState.enemies.Remove(enemy); } })
这个方案仅在初始化_tempRemovalList时产生一次内存分配,后续每帧操作都不会新增分配,同时保持了逻辑的清晰性。
其他基于Observable的实现方法
如果不介意少量内存分配(适合元素数量不多的场景),可以用以下链式写法:
1. 使用ToList()/ToArray()提前复制集合
这是你提到的方案,本质是先复制原集合到新列表,避免枚举原集合时被修改:
Observable.EveryUpdate() .SelectMany(_ => _gameState.enemies.ToList()) // 复制到新列表,避免枚举原集合 .Where(enemy => enemy.x > 5) .Subscribe(enemy => { _gameState.enemies.Remove(enemy); })
2. 用Select收集待移除元素后批量处理
Observable.EveryUpdate() .Select(_ => _gameState.enemies.Where(e => e.x > 5).ToList()) .Subscribe(enemiesToRemove => { foreach (var enemy in enemiesToRemove) { _gameState.enemies.Remove(enemy); } })
这种方式将筛选和移除分离,逻辑更清晰,但每次都会创建新列表,存在一定内存开销。
3. 使用Aggregate聚合待移除元素
Observable.EveryUpdate() .Select(_ => _gameState.enemies.Aggregate(new List<Enemy>(), (list, enemy) => { if (enemy.x > 5) list.Add(enemy); return list; })) .Subscribe(enemiesToRemove => { foreach (var enemy in enemiesToRemove) { _gameState.enemies.Remove(enemy); } })
和ToList()方案类似,本质还是创建新集合存储待移除元素,适合需要在链式操作中完成聚合的场景。
内容的提问来源于stack exchange,提问作者Eduardo Páez Rubio
相关产品推荐
相关产品推荐

