当foreach迭代时,无法在集合中添加/删除项目

时间:2010-06-14 20:51:22

标签: c# ienumerable

如果我自己实现IEnumerator接口,那么我可以(在foreach语句内)在albumsList内添加或删除项目而不会产生异常。但是如果{ {1}}语句使用foreach提供的IEnumerator,然后尝试在albumsList中添加/删除(在foreach中)项将导致异常:

albumsList

a)我假设抛出异常的代码(使用class Program { static void Main(string[] args) { string[] rockAlbums = { "rock", "roll", "rain dogs" }; ArrayList albumsList = new ArrayList(rockAlbums); AlbumsCollection ac = new AlbumsCollection(albumsList); foreach (string item in ac) { Console.WriteLine(item); albumsList.Remove(item); //works } foreach (string item in albumsList) { albumsList.Remove(item); //exception } } class MyEnumerator : IEnumerator { ArrayList table; int _current = -1; public Object Current { get { return table[_current]; } } public bool MoveNext() { if (_current + 1 < table.Count) { _current++; return true; } else return false; } public void Reset() { _current = -1; } public MyEnumerator(ArrayList albums) { this.table = albums; } } class AlbumsCollection : IEnumerable { public ArrayList albums; public IEnumerator GetEnumerator() { return new MyEnumerator(this.albums); } public AlbumsCollection(ArrayList albums) { this.albums = albums; } } } 提供的IEnumerator实现A时)位于albumsList内?

b)如果我希望能够从集合中添加/删除项目(当A迭代它时),我是否总是需要提供我自己的foreach接口实现,或者可以将albumsList设置为允许添加/删除项吗?

谢谢

4 个答案:

答案 0 :(得分:15)

最简单的方法是反转for(int i = items.Count-1; i >=0; i--)之类的项目,或循环一次,收集要在列表中删除的所有项目,然后循环删除项目,将其从原始列表中删除。

答案 1 :(得分:13)

通常不鼓励设计允许您在枚举时修改集合的集合类,除非您打算专门设计一些线程安全的东西以便这样做(例如,从一个线程添加而从另一个线程枚举)。 / p>

原因无数。这是一个。

您的MyEnumerator课程通过递增内部计数器来工作。其Current属性在ArrayList中公开给定索引处的值。这意味着枚举集合并删除“每个”项目实际上不会按预期工作(即,它不会删除列表中的每个项目。)

考虑这种可能性:

您发布的代码实际上会执行此操作:

  1. 首先将索引递增为0,这样可以得到Current的“摇滚”。你删除“摇滚。”
  2. 现在该集合已["roll", "rain dogs"],您将索引增加到1,使Current等于“雨狗”(不是“滚动”)。接下来,你删除“雨狗”。
  3. 现在该集合已["roll"],您将索引增加到2(&gt; Count);所以你的普查员认为它已经完成了。
  4. 但是,这是一个有问题的实施的其他原因。例如,使用您的代码的人可能无法理解您的枚举器如何工作( 他们 - 实现应该无关紧要),因此没有意识到调用Remove的成本foreach块会在每次迭代时产生IndexOf - 即线性搜索 - 的惩罚(请参阅the MSDN documentation on ArrayList.Remove进行验证)。

    基本上,我得到的是:你不想要能够从foreach循环中删除项目(再次,除非你设计一些线程-safe ... 也许)。

    好的,那么替代方案是什么?以下几点可以帮助您入门:

    1. 不要将您的集合设计为允许 - 更不用说期望 - 在枚举中进行修改。它导致了奇怪的行为,例如我上面提供的例子。
    2. 相反,如果您想提供批量删除功能,请考虑Clear(删除所有项目)或RemoveAll等方法(删除与指定过滤条件匹配的项目) )。
    3. 这些批量删除方法可以相当容易地实现。 ArrayList已经有一个Clear方法,就像你在.NET中可能使用的大多数集合类一样。否则,如果内部集合已编制索引,则删除多个项目的常用方法是使用for循环从顶部索引枚举,并在需要删除的索引上调用RemoveAt(注意这解决了两个问题)立即:通过从顶部向后退,您可以确保访问集合中的每个项目;此外,通过使用RemoveAt代替Remove,您可以避免重复线性搜索的惩罚。
    4. 作为补充说明,我强烈建议首先明确指出ArrayList等非通用集合。使用强类型的通用副本(例如List(Of Album))(假设您有Album类 - 否则,List(Of String)仍然比ArrayList更安全。

答案 2 :(得分:0)

假设我有一个集合,一个数组

int[] a = { 1, 2, 3, 4, 5 };

我有一个功能

   public IList<int> myiterator()
        {
            List<int> lst = new List<int>();
            for (int i = 0; i <= 4; i++)
            {
                lst.Add(a[i]);
            }

              return lst;
        }

现在我调用此函数并迭代并尝试添加

   var a = myiterator1();
    foreach (var a1 in a)
       {
         a.Add(29);
       }

会导致运行时异常

这里需要注意的是,如果允许我们为每个元素添加 在列表中

列表将变为{1,2,3,4,5,6} 然后对于每个元素和每个新添加的我们继续添加它的coz 我们将陷入无限的操作,因为它将再次为每个元素重复

答案 3 :(得分:-1)

来自INotifyCollectionChanged的MSDN文档:

  

您可以枚举任何集合   实现IEnumerable   接口。但是,要设置动态   绑定,以便插入或   集合中的删除更新了   UI自动,集合必须   实现INotifyCollectionChanged   接口。这个界面暴露了   必须是CollectionChanged事件   什么是潜在的   收集变化。

     

WPF提供了   ObservableCollection&lt;(Of&lt;(T&gt;)&gt;)   class,这是一个内置的   实施数据收集   暴露了   INotifyCollectionChanged接口。   有关示例,请参见如何:创建和   绑定到ObservableCollection。

     

中的各个数据对象   收藏必须满足   绑定中描述的要求   来源概述。

     

在实施自己的之前   收集,考虑使用   ObservableCollection&lt;(Of&lt;(T&gt;)&gt;)或   现有的收藏品之一   类,例如List&lt;(Of&lt;(T&gt;)&gt;),   收集&lt;(&lt;(T&gt;)&gt;),和   BindingList&lt;(Of&lt;(T&gt;)&gt;)等等   其他

     

如果您有高级方案并且   想要实现自己的收藏,   考虑使用IList,它提供了一个   非泛型的对象集合   可以通过索引单独访问   并提供最佳表现。

听起来我的问题出在Collection本身,而不是它的Enumerator。