【问题标题】:Problems removing elements from a list when iterating through the list迭代列表时从列表中删除元素的问题
【发布时间】:2011-04-02 12:33:56
【问题描述】:

我有一个循环遍历列表中的元素。我需要根据某些条件从循环内的此列表中删除元素。当我尝试在 C# 中执行此操作时,出现异常。显然,不允许从正在迭代的列表中删除元素。使用 foreach 循环观察到该问题。有没有解决这个问题的标准方法?

注意:我能想到的一种解决方案是仅出于迭代目的创建列表的副本,并从循环内的原始列表中删除元素。我正在寻找一种更好的方法来解决这个问题。

【问题讨论】:

    标签: c# loops foreach


    【解决方案1】:

    当使用List<T> 时,ToArray() 方法在这种情况下有很大帮助:

    List<MyClass> items = new List<MyClass>();
    foreach (MyClass item in items.ToArray())
    {
        if (/* condition */) items.Remove(item);
    }
    

    另一种方法是使用 for 循环而不是 foreach,但是每次删除元素时都必须减少索引变量,即

    List<MyClass> items = new List<MyClass>();
    for (int i = 0; i < items.Count; i++)
    {
        if (/* condition */)
        {
            items.RemoveAt(i);
            i--;
        }
    }
    

    【讨论】:

    • 如果您使用的是List&lt;T&gt;,那么为什么不在这种情况下使用内置的RemoveAll 方法呢?
    • 我避免使用将委托作为参数的方法,因为您必须创建新方法或将匿名方法添加到当前块。如果您这样做,您的代码在调试时将与编辑并继续不兼容。
    • 为什么 ToArray() 中有不必要的复制操作?
    • 其实我一直在想这个。由于通用 List 类是使用数组实现的,因此每次调用 Remove() 都会带来很大的开销。声明一个新列表(其初始容量设置为第一个列表中的项目数)实际上会更有效,将每个不符合删除条件的元素添加到新列表中,然后将新列表分配给包含旧列表的变量。
    【解决方案2】:

    如果您的列表是实际的List&lt;T&gt;,那么您可以使用内置的RemoveAll 方法根据谓词删除项目:

    int numberOfItemsRemoved = yourList.RemoveAll(x => ShouldThisItemBeDeleted(x));
    

    【讨论】:

    • 这个方法假设你有一个过滤器。当你不知道的时候没用。
    • @Lord Black:该问题明确指出应该删除项目“基于某些条件”;我假设的 ShouldThisItemBeDeleted 函数将是这些条件的表达式。
    【解决方案3】:

    您可以使用整数索引来删除项目:

    List<int> xs = new List<int> { 1, 2, 3, 4 };
    for (int i = 0; i < xs.Count; ++i)
    {
        // Remove even numbers.
        if (xs[i] % 2 == 0)
        {
            xs.RemoveAt(i);
            --i;
        }
    }
    

    不过,这可能读起来很奇怪,也很难维护,尤其是在循环中的逻辑变得更加复杂的情况下。

    【讨论】:

      【解决方案4】:

      您可以使用 LINQ 通过过滤掉项目来用新列表替换初始列表:

      IEnumerable<Foo> initialList = FetchList();
      initialList = initialList.Where(x => SomeFilteringConditionOnElement(x));
      // Now initialList will be filtered according to the condition
      // The filtered elements will be subject to garbage collection
      

      这样你就不用担心循环了。

      【讨论】:

      • 或者,如果列表是List&lt;T&gt;,那么您可以使用RemoveAll 方法。
      【解决方案5】:

      另一个技巧是向后循环列表。删除一个项目不会影响您将在循环的其余部分遇到的任何项目。

      我不推荐这个或其他任何东西。您需要的所有内容都可以使用 LINQ 语句根据您的要求过滤列表来完成。

      【讨论】:

        【解决方案6】:

        您可以通过这种方式使用 foreach 进行迭代:

        List<Customer> custList = Customer.Populate();
        foreach (var cust in custList.ToList())
        {
            custList.Remove(cust);
        }
        

        注意:ToList 上的变量列表,这会遍历由 ToList 创建的列表,但会从原始列表中删除项目。

        希望这会有所帮助。

        【讨论】:

        • 哇....这对我来说开箱即用!彻底解决了我的问题。有时列表的底部有正确的答案(至少对我来说是正确的)。
        • 同样,这是我的赢家。干得好。
        • 谢谢各位,向下滚动一下,您会找到简短而简单的答案
        【解决方案7】:

        推荐的解决方案是将您要删除的所有元素放在一个单独的列表中,并在第一个循环之后,放置第二个循环,在其中迭代删除列表并从第一个列表中删除这些元素。

        【讨论】:

        • 谁推荐的?这是一种毫无意义的昂贵方法,没有任何好处。
        【解决方案8】:

        您收到错误的原因是您使用了 foreach 循环。如果您考虑一下 foreach 循环的工作原理,这是有道理的。 foreach 循环调用 List 上的 GetEnumerator 方法。如果您在哪里更改列表中的元素数量,则 foreach 循环所包含的枚举器将没有正确数量的元素。如果您删除了一个元素,则会引发空异常错误,如果您添加了一个元素,则循环将错过一个项目。

        如果你喜欢 Linq 和 Lamda 表达式,我会推荐 Darin Dimitrov 解决方案,否则我会使用 Chris Schmich 提供的解决方案。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2018-07-18
          • 2016-07-19
          • 2015-06-30
          • 2020-11-25
          • 2013-01-23
          • 2016-11-21
          • 1970-01-01
          • 2013-11-25
          相关资源
          最近更新 更多