【问题标题】:How can I do this special foreach iterator? [duplicate]我该如何做这个特殊的 foreach 迭代器? [复制]
【发布时间】:2010-12-08 21:43:04
【问题描述】:

可能重复:
How to modify or delete items from an enumerable collection while iterating through it in C#

听着,我不想了解基本的 foreach。我说的是控制这个错误的那个:

“枚举器无效,因为集合已更改。”

当我这样做时会发生:

foreach(Image image in images)
{
   if(...)
   {
       images.remove(image)
   }
}

我相信有一个特殊的迭代器可以很好地处理这个问题,就像 Java 一样。 那么,我该如何在 C# 中做到这一点? 谢谢!

【问题讨论】:

    标签: c# .net


    【解决方案1】:

    或者直接删除它而不需要手动迭代:

    images.RemoveAll(image=>...)
    

    适用于 List<T>,但许多其他容器不支持它。

    IList<T> 上工作的 O(n) 解决方案:

    void RemoveWhere(this IList<T> list,Predicate<T> pred)
    {
        int targetIndex=0;
        for(int srcIndex=0;srcIndex<list.Count;srcIndex++)
        {
          if(pred(list[srcIndex]))
          {
            list[targetIndex]=list[srcIndex];
            targetIndex++;
          }
          for(int i=list.Count-1;i>=targetIndex;i--)
            list.RemoveAt(i);
        }
    }
    

    在您点击第一个删除的项目之前不分配可以加快速度。

    【讨论】:

    • 在我的情况下,我只会在条件为真时删除。
    • lamda 是一个条件。函数的名称具有误导性。我会称它为RemoveWhere
    • 唉,RemoveAll 是 LINQ 扩展方法之一,因此将其命名为其他方法几乎不是一种选择。不过,我同意这种方法以及其他一些方法的命名很糟糕。
    • 噢!无视我之前的评论。我的大脑一定在别的地方; RemoveAll 不是 LINQ 方法。我应该意识到,因为 LINQ 扩展方法不执行操作。所以无论如何,我想我只是同意 RemoveAll 方法的名字很糟糕。
    【解决方案2】:
    for (var i = 0; i < images.Count; ++i)
    {
        if (...)
        {
            images.RemoveAt(i);
            --i;
        }
    }
    

    【讨论】:

    • 但是如果我这样做,我相信我会失去下一个对象,考虑到我正在使用 Canvas。例如:我删除了索引 2 的对象的图像。因此索引 2 的对象将作为列表移动到索引 2。因此,当我尝试获取下一个值即索引 3 时,索引 3 的旧对象将位于索引 2。所以我会失去他。
    • @Alan,这就是--i; 行的目的。这会调整索引,以便不会遗漏任何项目。
    • 这就是--i 更正的内容。删除时递减的技术很有趣。我通常写我的循环来回溯并有时进行双重测试。
    • 对于大多数容器来说这是 O(n^2)。在IList&lt;T&gt;上写O(n)时间和O(1)空间解是可能的
    • 如果要删除项目,有时也更容易向后循环:for (int i = images.Count - 1; i
    【解决方案3】:

    你不能在 C# 中做到这一点。

    你可以做的是收集你想要移除的对象,然后移除它们:

    Image[] imagesToRemove = images.Where(image => ...).ToArray();
    foreach (Image image in imagesToRemove)
        images.remove(image);
    

    【讨论】:

    • 我认为您的代码通常是 O(n^2),因为大多数集合上的 Remove 是 O(n)。在HashSet&lt;T&gt; 上它会很快,但 HashSet 已经为此提供了内置方法。
    【解决方案4】:

    如果实现了IList&lt;T&gt;,肯特的答案将有效。对于没有的东西,你将不得不建立一个你想要删除的东西的列表。例如:

    public static void RemoveWhere<T>(this ICollection<T> self, Func<T, bool> predicate)
    {
        var toRemove = self.Where(predicate).ToList();
    
        foreach (var i in toRemove)
            self.Remove(i);
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-06-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-06-06
      • 2015-05-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多