修改Dictionary键并迭代时触发集合修改异常,如何解决?
首先,咱们先搞清楚你遇到的The Collection has been modified enumeration operator may not execute异常的根本原因:当你用foreach枚举字典的Keys(或Values、直接枚举字典)时,.NET的枚举器会锁定集合的状态,不允许在枚举过程中添加/删除元素——因为枚举器是基于当前集合的快照工作的,一旦集合被修改,枚举器就失效了,自然会抛出这个异常。
看你的代码,问题出在这段循环逻辑里:
foreach (var container in SuggestedDictionary.Keys) { rec.Add(new Recommendations() { bookName = container, Rate = CalculatePearsonCorrelation(bkName, container) }); } SuggestedDictionary.Remove(SuggestedDictionary.Keys.ToString()); if (!SuggestedDictionary.ContainsKey(entry[i].bookName)) { SuggestedDictionary.Add(entry[i].bookName, list); }
你在遍历SuggestedDictionary.Keys的同一个迭代周期内,紧接着就对原字典执行了Remove和Add操作——哪怕修改操作不在foreach循环内部,只要枚举器还在生效期间修改了集合,就会触发异常。
直接修复方案:遍历键的副本
最简单的修复方式是把要遍历的键先复制到一个独立的列表里,这样遍历的是副本,原字典的修改不会影响枚举过程。把遍历Keys的代码改成这样:
// 把Keys转成List,遍历副本而非原集合 foreach (var container in SuggestedDictionary.Keys.ToList()) { rec.Add(new Recommendations() { bookName = container, Rate = CalculatePearsonCorrelation(bkName, container) }); }
顺便修正代码里的其他潜在问题
除了枚举异常,你的代码还有几个需要注意的点:
填充列表的无限循环风险:
List<Entity> list = db.books.ToList(); foreach(var l in list) { list.Add(l); }这行代码会导致无限循环——因为你在遍历
list的同时往里面添加元素,list的长度会一直增加,永远遍历不完。其实db.books.ToList()已经把所有书籍数据取到list里了,后面的foreach完全多余,直接删掉就行。错误的键移除方式:
SuggestedDictionary.Remove(SuggestedDictionary.Keys.ToString());SuggestedDictionary.Keys是一个键集合,ToString()得到的是集合的字符串表示(比如System.Collections.Generic.Dictionary2+KeyCollection`),根本不是你要移除的键。如果你是想移除当前字典里的唯一键(因为你初始只加了一个"Key"),可以改成:if (SuggestedDictionary.Any()) { var oldKey = SuggestedDictionary.Keys.First(); SuggestedDictionary.Remove(oldKey); }这样能安全地移除第一个键,还避免了字典为空时的报错。
更优的实现思路
如果你的核心需求是反复替换字典的键,然后基于当前键计算推荐,其实没必要频繁修改字典——字典的优势是快速查找,但如果每次只保留一个键,直接用一个变量存储当前键会更高效:
// 用变量代替字典存储当前键,避免频繁修改字典 string currentKey = "Key"; List<Entity> currentList = db.books.ToList(); foreach (var entry in CopyDataDict.Values.ToList()) { for (int i = 2; i < 15; i++) { // 直接用currentKey计算推荐 rec.Add(new Recommendations() { bookName = currentKey, Rate = CalculatePearsonCorrelation(bkName, currentKey) }); // 替换当前键 currentKey = entry[i].bookName; // 如果需要保留列表关联,也可以用字典,但只维护单个键的话变量更高效 } }
如果确实需要维护字典(比如后续可能有多个键),可以考虑用ConcurrentDictionary(线程安全字典),它支持在枚举时修改集合,但如果你的场景不是多线程,用前面的ToList()复制副本的方案就足够了。
内容的提问来源于stack exchange,提问作者Muhammad Shahid

