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

flo*_*ode 4 c# ienumerable

如果我自己实现IEnumerator接口,那么我能够(内部foreach语句)在albumsList不产生异常的情况下添加或删除项目.但是如果foreach语句使用了IEnumerator提供的albumsList,那么尝试添加/删除(在foreach内部)项目albumsList将来自导致异常:

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;
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

a)我假设抛出异常的代码(当使用提供的IEnumerator实现时)位于里面?AalbumsListA

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

谢谢

Ste*_*per 15

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

  • +1 ...迭代/删除项目时从后面开始. (2认同)

Dan*_*Tao 13

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

原因无数.这是一个.

您的MyEnumerator课程通过递增内部计数器来工作.它的Current属性暴露了给定索引的值ArrayList.这意味着枚举集合并删除"每个"项目实际上将无法按预期工作(即,它不会删除列表中的每个项目).

考虑这种可能性:

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

  1. 首先将索引递增为0,这会给你一个Current"摇滚".你删除"摇滚".
  2. 现在集合了["roll", "rain dogs"],你增加你的指数为1,使得Current等于"太阳雨"(而不是"卷") .接下来,你删除"雨狗".
  3. 现在集合已经["roll"],你将索引增加到2(这是> Count); 所以你的普查员认为它已经完成了.

不过,还有其他原因,这是一个有问题的实现.例如,使用你的代码的人可能不理解你的枚举器是如何工作的(他们也不应该 - 实现应该无关紧要),因此没有意识到Remove在一个foreach块内调用的成本会受到惩罚IndexOf- 即线性搜索- 每次迭代(请参阅MSDN文档ArrayList.Remove以验证这一点).

基本上,我得到的是:你不希望能够从一个foreach循环内删除项目(再次,除非你设计一些线程安全的东西...... 也许).

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

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