【问题标题】:Can't add/remove items from a collection while foreach is iterating over it无法在 foreach 迭代集合时从集合中添加/删除项目
【发布时间】:2011-03-03 17:16:50
【问题描述】:

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

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

}

a) 我假设抛出异常的代码(使用 A 提供的 A 提供的 IEnumerator 时)位于 A 内?

b) 如果我希望能够从集合中添加/删除项目(foreach 正在对其进行迭代),我是否总是需要提供我自己的 IEnumerator 接口实现,或者可以设置 AlbumsList允许添加/删除项目?

谢谢

【问题讨论】:

    标签: c# ienumerable


    【解决方案1】:

    来自INotifyCollectionChanged 的 MSDN 文档:

    您可以枚举任何集合 实现 IEnumerable 界面。但是,要设置动态 绑定,以便插入或 集合中的删除更新 UI 自动,集合必须 实施 INotifyCollectionChanged 界面。这个接口暴露了 必须是 CollectionChanged 事件 每当底层证券提出 集合更改。

    WPF 提供 ObservableCollection)>) 类,这是一个内置的 数据收集的实施 这暴露了 INotifyCollectionChanged 接口。 例如,请参阅如何:创建和 绑定到 ObservableCollection。

    内部的各个数据对象 集合必须满足 绑定中描述的要求 来源概览。

    在实现自己的之前 收藏,考虑使用 ObservableCollection)>) 或 现有收藏之一 类,例如 List)>), 集合)>),和 BindingList)>),在众多 其他人。

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

    在我看来问题出在集合本身,而不是它的枚举器。

    【讨论】:

    • 这和问题有关系吗?
    • 是的。如果您正在迭代的 Collection 尚未实现 INotifyCollectionChanged,则尝试从 foreach 块中进行修改将引发异常。这描述了问题的原因,并指出了如何解决它。
    • @W. Craig Trader:我认为 LukeH 的问题源于这样一个事实,即没有任何明确的证据表明 OP 询问与 UI 的交互。此外,实现INotifyCollectionChanged 并不会神奇地允许您在foreach 块中添加/删除。
    • "是的。如果您正在迭代的集合尚未实现 INotifyCollectionChanged,那么尝试从 foreach 块中进行修改将引发异常" 但是集合 AlbumsCollection 没有实现 INotifyCollectionChanged,并且然而 foreach 块不会抛出异常
    • “我认为 LukeH 的问题源于这样一个事实,即没有任何明确的证据表明 OP 询问的是与 UI 的交互。”我最近才开始学习编程,所以我还没有涉及任何 UI 技术(例如 Asp.Net)
    【解决方案2】:

    最简单的方法是反向遍历 for(int i = items.Count-1; i &gt;=0; i--) 之类的项目,或者循环一次,将所有要删除的项目收集到一个列表中,然后循环遍历要删除的项目,将它们从原始列表中删除。

    【讨论】:

    • 在搜索和修改任何类型的文本时,我从后面开始的原因相同:“下一个”项目的位置不会改变。 :)
    • 也许,for(int i = items.Count - 1; ... 应该更好?
    【解决方案3】:

    通常不鼓励设计允许您在枚举时修改集合的集合类,除非您的意图是专门设计一些线程安全的东西以便这成为可能(例如,从一个线程添加同时从另一个线程枚举)。

    原因有很多。这是一个。

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

    考虑这种可能性:

    您发布的代码实际上会这样做:

    1. 您首先将索引增加到 0,这会为您提供“摇滚”的Current。你删除了“rock”。
    2. 现在集合有 ["roll", "rain dogs"] 并且您将索引增加到 1,使 Current 等于“rain dogs”(不是“roll”)。接下来,删除“rain dogs”。
    3. 现在集合有["roll"],你将索引增加到2(即>Count);所以你的枚举员认为它已经完成了。

    不过,这是一个有问题的实现还有其他原因。例如,使用您的代码的人可能不了解您的枚举器是如何工作的(他们也不应该应该他们 - 实现应该真的不重要),因此没有意识到在 @987654331 中调用 Remove 的成本@block 在每次迭代时都会导致IndexOf 的惩罚——即线性搜索(请参阅the MSDN documentation on 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 更安全)。

    【讨论】:

    • 说得好。如果可以的话,我会给它+2。
    • 只是出于好奇 - 是不是 albumsList 提供的枚举器实际检测到(并因此引发异常)我们正在尝试删除/添加元素?无论如何,将遵循您的指导方针
    • @flockofcode:是和不是。实际上,ArrayList.GetEnunerator 的工作方式是创建一个引用底层 ArrayList 对象的对象,该对象维护一个数字来表示其状态。当在枚举器上调用MoveNext 时,它会根据构造枚举器时的值检查这个数字,如果数字不匹配则抛出异常。所以它是ArrayList 和导致抛出异常的枚举器之间的交换。
    • 如果 IEnumerable,Microsoft 没有定义变体,对集合修改方案的处理方式不同,这太糟糕了。我希望至少有两个是 IMultipassEnumerable,它的枚举器将包括 Reset 和 Count 方法,并保证多次传递会产生相同的项目,IModifiableEnumerable,这将允许在枚举期间进行修改(尽管不一定是线程安全的,并且没有保证何时可以观察到修改)而不破坏枚举器和 ThreadSafeModifiableEnumerator。
    【解决方案4】:

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

    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} 然后对于每个元素和每个新添加的元素,我们都会继续添加因为 我们将陷入无限操作,因为它将再次对每个元素重复

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-05-15
      • 2010-12-13
      • 2020-07-27
      • 2012-05-13
      相关资源
      最近更新 更多