【问题标题】:Best practice to avoid InvalidOperationException: Collection was modified?避免 InvalidOperationException 的最佳实践:集合已修改?
【发布时间】:2011-01-17 22:47:01
【问题描述】:

我经常需要这样的东西:

 foreach (Line line in lines)
 {
    if (line.FullfilsCertainConditions())
    {
       lines.Remove(line)
    }
 }

这不起作用,因为我总是得到InvalidOperationException,因为枚举器在循环期间发生了变化。

因此,我将所有此类循环更改为以下内容:

List<Line> remove = new List<Line>();
foreach (Line line in lines)
{
   if (line.FullfilsCertainConditions())
   {
      remove.Add(line)
   }
}

foreach (Line line in remove) {
{
   lines.Remove(line);
}

我不确定这是否真的是最好的方法,因为在最坏的情况下,我必须在原始列表上迭代 2 次,因此它需要时间 2n 而不是 n。

有没有更好的方法来做到这一点?

编辑:

我可以使用 Mark 的回答来做到这一点!但是如果我的集合没有实现 RemoveAll() 怎么办?

例如一个

System.Windows.Controls.UIElementCollection

编辑 2:

再次在 Mark 的帮助下,我现在可以进行以下调用以删除所有 ScatterViewItems:

CollectionUtils.RemoveAll(manager.getWindow().IconDisplay.Items, elem => elem.GetType() == typeof(ScatterViewItem));

【问题讨论】:

  • 我前段时间遇到了同样的问题,但没有解决方案。更糟糕的是 - 它不是 2n 而是 n^2,因为 lines.Remove(line) 再次遍历集合。
  • 但是 O(2n) 确实等于 O(n) :-):在 Java 中,您可以使用 Iterator 来执行此操作,或者使用 Copy-on-Write Collection 实现,它允许在迭代时进行修改.
  • @Marc Gravell 非常感谢!不幸的是,我无法再次投票!

标签: c# .net


【解决方案1】:

这是直接烘焙到List&lt;T&gt;

lines.RemoveAll(line => line.FullfilsCertainConditions());

或在 C# 2.0 中:

lines.RemoveAll(delegate(Line line) {
    return line.FullfilsCertainConditions();
});

在非List&lt;T&gt; 情况下(您对问题的编辑),您可以将其包装如下(未经测试):

static class CollectionUtils
{
    public static void RemoveAll<T>(IList<T> list, Predicate<T> predicate)
    {
        int count = list.Count;
        while (count-- > 0)
        {
            if (predicate(list[count])) list.RemoveAt(count);
        }
    }
    public static void RemoveAll(IList list, Predicate<object> predicate)
    {
        int count = list.Count;
        while (count-- > 0)
        {
            if (predicate(list[count])) list.RemoveAt(count);
        }
    }
}

由于UIElementCollection 实现了(非通用)IList,这应该可以工作。而且非常方便,使用 C# 3.0,您可以在 IList / IList&lt;T&gt; 之前添加 this 并将其作为扩展方法。唯一的微妙之处在于 anon-method 的参数将是 object,因此您需要将其丢弃。

【讨论】:

  • 有没有办法在 .NET 2.0 中实现同样的效果?
  • @Matten - 添加了一个 C# 2.0 示例。
  • @Marc Gravell - 非常简单,非常优雅。谢谢:)
  • @Matten - .NET 2.0 还是 C# 2.0? .NET 2.0 支持 LINQ,它是 C# 3.0 的一部分。 csharpindepth.com/Articles/Chapter1/Versions.aspx
  • 只是为了迂腐——当面向 .NET 2.0 时,您可以使用 C# 3.0 中的顶级 (lambda) 版本; lambda 是(至少在这种情况下)一种语言特性,而不是运行时特性。
【解决方案2】:

您可以简单地将原始列表替换为过滤后的列表:

lines = lines.Where(line => line.FullfilsCertainConditions()).ToList();

【讨论】:

    【解决方案3】:

    建立一个新的列表:

    public IList<Line> GetListWithoutFullfilsCertainConditions(IList<Line> fullList) 
    {
        IList<Line> resultList = new List<Line>(fullList.Count);
    
        foreach (Line line in fullList)
        {
           if (!line.FullfilsCertainConditions())
           {
              resultList.Add(line)
           }
        }
    
        return resultList;
    }
    

    【讨论】:

      【解决方案4】:

      你也可以只使用while循环。

      int i = 0;
      while(i < lines.Count)
      {
        if (lines[i].FullfilsCertainConditions())
        {
           lines.RemoveAt(i);
        }
        else {i++;}
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-09-12
        • 2019-04-03
        • 1970-01-01
        • 1970-01-01
        • 2011-08-11
        • 2012-02-19
        相关资源
        最近更新 更多