C#实现观察者模式遍历集合报集合已修改枚举操作异常
异常产生原因
- 直接触发点:你在遍历
animals集合的foreach循环内部执行了animals.Remove()修改原集合的操作。.NET 中List<T>的枚举器有内置状态校验,只要枚举过程中原集合发生新增、删除元素的操作,就会直接抛出该异常。你之前尝试ToList()/ToArray()方案无效,是因为没有把快照逻辑用对位置,也没有把修改原集合的操作移出遍历周期。 - 代码逻辑错误:你当前
stock == 0分支的删除逻辑写在了遍历animals的循环块内部,属于典型的「边遍历边修改原集合」场景,必然触发枚举校验异常。 - 隐藏隐患:所有遍历
observers集合调用观察者回调的位置,如果观察者在OnNext/OnCompleted回调中执行Unsubscribe()操作,会直接修改observers原集合,同样会触发同类异常,只是当前测试场景没有走到该路径。 - 附带逻辑bug:
StockScreen的OnNext方法中,处理库存为0的动物时,刚把待删除项加入临时列表就立刻清空临时列表,导致本地缓存的下架动物信息永远无法被清理。
修复步骤
- 重构
stock == 0分支的逻辑,严格遵循「先遍历原集合快照收集待处理项→遍历观察者快照发通知→遍历完成后统一修改原集合」的流程,彻底把遍历和修改操作隔离开:
else if (stock == 0) { var animalsToRemove = new List<Animal>(); // 遍历前对animals做快照,避免枚举过程受原集合修改影响 foreach(var animal in animals.ToList()) { if(info.AnimalId == animal.AnimalId) { animalsToRemove.Add(animal); } } // 通知观察者前对observers做快照,避免观察者回调中取消订阅触发集合修改异常 foreach (var observer in observers.ToList()) { observer.OnNext(info); } // 所有遍历、通知操作完成后,再统一删除原集合中的元素 foreach (var animalToRemove in animalsToRemove) { animals.Remove(animalToRemove); } }
- 修复所有遍历观察者集合的位置,统一加快照逻辑,覆盖所有通知路径:
- 新增动物成功后的通知代码修改为:
animals.Add(info); foreach (var observer in observers.ToList()) { observer.OnNext(info); }
LastAnimalBought方法中的完成通知修改为:
public void LastAnimalBought() { foreach (var observer in observers.ToList()) { observer.OnCompleted(); } observers.Clear(); }
- 修复
StockScreen中下架动物的本地缓存清理逻辑,修正临时列表清空时机:
if (info.AnimalStock == 0) { var animalsToRemove = new List<string>(); string name = String.Format("{0,5}", info.AnimalId); // 遍历本地缓存快照收集待删除项 foreach(var animalInfo in animalInfos.ToList()) { if (animalInfo.Substring(21, 5).Equals(name)) { animalsToRemove.Add(animalInfo); } } // 统一删除本地缓存中的下架项 foreach (var item in animalsToRemove) { animalInfos.Remove(item); updated = true; } animalsToRemove.Clear(); }
内容的提问来源于stack exchange,提问作者Dennis.b
相关产品推荐
相关产品推荐

