如何在 C# 中遍历集合时添加或删除对象

17

我想在遍历集合时删除对象,但是我遇到了异常。我该怎么做呢? 这是我的代码:

foreach (var gem in gems)
{
    gem.Value.Update(gameTime);

    if (gem.Value.BoundingCircle.Intersects(Player.BoundingRectangle))
    {
       gems.Remove(gem.Key); // I can't do this here, then How can I do?
       OnGemCollected(gem.Value, Player);
    }
}

8
尝试使用 foreach (var gem in gems.ToList())。该语句的意思是对于 gems.ToList() 中的每一个元素,使用变量 gem 进行循环遍历。 - I4V
2
使用foreach循环时不能删除元素。请使用简单的for循环,因为foreach不是为此设计的。 - Prabhu Murthy
1
Dictionary 实现了 IEnumerable<> 接口(这就是为什么你可以通过它进行 foreach 循环)。ToList 是一个 LINQ 扩展方法,如果你添加了 using System.Linq; 命名空间引用,它应该能够使用。 - Tim S.
@HakooDesai 是的,@IV4 和@TimS 是正确的:ToList() 可以工作,因为它生成 KeyValuePair 列表,而不是 Gem 的列表。 - Matthew Watson
@HakooDesai:字典的类型是什么? - Shahar G.
显示剩余4条评论
6个回答

30

foreach循环被设计用于在不修改集合的情况下迭代它。

如果要在迭代集合时删除其中的项,请使用从结尾到开头的for循环。

for(int i = gems.Count - 1; i >=0 ; i--)
{
  gems[i].Value.Update(gameTime);

  if (gems[i].Value.BoundingCircle.Intersects(Player.BoundingRectangle))
  {
      Gem gem = gems[i];
      gems.RemoveAt(i); // Assuming it's a List<Gem>
      OnGemCollected(gem.Value, Player);
  }
 }
如果它是一个dictionary<string, Gem>,你可以像这样进行迭代:
foreach(string s in gems.Keys.ToList())
{
   if(gems[s].BoundingCircle.Intersects(Player.BoundingRectangle))
   {
     gems.Remove(s);
   }
}

@MatthewWatson:在这种情况下,如果我们假设键也作为属性在值中可用,我们可以很好地使用dictionary.Values.ToList()和for循环遍历字典,然后调用dictionary.Remove(item.Key); - Saravanan
@Saravanan,从OP的代码来看,似乎键与值是分开的。 - Matthew Watson
1
为什么这么简单的方法我没想到!!!感谢大家宝贵的建议。 - Hakoo Desai
1
只需按照第一条评论中的建议使用 foreach (var gem in gems.ToList()) 更容易。你只需要添加 .ToList(),就可以让所有代码都正常工作,而无需更改其他任何代码!你似乎认为它不起作用,但这意味着你没有尝试过,因为它绝对可以正常工作。 - Matthew Watson
倒序循环真的很聪明!我通常是正向循环,每当我从列表中删除某些内容时就递减。那样更加优雅。 - pjrader1
foreach (var gem in gems.ToList()) 绝对是最简单的解决方案,需要改动的代码最少。对我来说完美地运作了。 - Rapid99

3

最简单的方法是按照 @IV4 的建议去做:

foreach (var gem in gems.ToList())
ToList() 方法可以把字典转换成 KeyValuePair 列表,这样就能正常运行了。
唯一不需要用这种方法的情况是,如果你有一个很大的字典,但只删除其中相对较少的项并且想要减少内存使用量。
只有在这种情况下,您才需要使用以下一种方法之一:
当您找到键时,请制作键列表,然后单独循环以删除项:
List<KeyType> keysToRemove = new List<KeyType>();

foreach (var gem in gems)
{
    gem.Value.Update(gameTime);

    if (gem.Value.BoundingCircle.Intersects(Player.BoundingRectangle))
    {
        OnGemCollected(gem.Value, Player);
        keysToRemove.Add(gem.Key);
    }
}

foreach (var key in keysToRemove)
    gems.Remove(key);

(其中KeyType为您正在使用的密钥类型。请更换正确的类型!)

或者,如果在调用OnGemCollected()之前删除宝石非常重要,则可以按以下方式进行操作(具有键类型TKey和值类型TValue):

var itemsToRemove = new List<KeyValuePair<TKey, TValue>>();

foreach (var gem in gems)
{
    gem.Value.Update(gameTime);

    if (gem.Value.BoundingCircle.Intersects(Player.BoundingRectangle))
        itemsToRemove.Add(gem);
}

foreach (var item in itemsToRemove)
{
    gems.Remove(item.Key);
    OnGemCollected(item.Value, Player);
}

1

在查看了所有答案并且同样出色之后,我面临一个挑战:我需要修改一个列表,最终我所做的事情对我来说效果很好。因此,如果有人发现它有用,可以给我提供反馈意见。请注意,保留HTML标签。

Action removeFromList;
foreach(var value in listOfValues){
    if(whatever condition to remove is){
        removeFromList+=()=>listOfValues.remove(value);
    }
}
removeFromList?.Invoke();
removeFromList = null;

1
正如其他答案所说,foreach是专门用于在不修改集合的情况下迭代集合的,详见文档

foreach语句用于遍历集合以获取所需信息,但不应用于更改集合内容以避免产生不可预测的副作用。

为了做到这一点,您需要使用for循环(存储需要删除的集合项),然后从集合中删除它们。
但是,如果您使用的是List<T>,则可以这样做:
lines.RemoveAll(line => line.FullfilsCertainConditions());

0

你应该使用for循环而不是foreach循环。请参考这里


1
不,移除会破坏索引。 - H H
2
如果你通过列表反向遍历,它会起作用。但这是一个无意义的观点,因为OP正在使用字典。 - Matthew Watson

0

集合支持使用枚举器的foreach语句。枚举器可用于读取集合中的数据,但不能用于修改底层集合。如果对集合进行更改,例如添加、修改或删除元素,则枚举器将被不可恢复地使无效,并且下一次调用MoveNext或Reset将引发InvalidOperationException异常。 使用for循环来修改集合。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接