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

如果我自己实现IEnumerator接口,那么我能够(在foreach语句中)添加或删除albumsList中的项而不生成exception。但是如果foreach语句使用了albumsList提供的IEnumerator ,那么尝试添加/删除(内部)来自albumsList的foreach项将导致exception:

 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)我假设抛出exception的代码(当使用由albumsList提供的IEnumerator实现时)位于A

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

谢谢

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

原因无数。 这是一个。

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

考虑这种可能性:

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

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

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

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

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

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

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

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

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

我有一个function

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

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

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

将导致运行时exception

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

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

从INotifyCollectionChanged的MSDN文档:

您可以枚举任何实现IEnumerable接口的集合。 但是,要设置动态绑定以便集合中的插入或删除自动更新UI,集合必须实现INotifyCollectionChanged接口。 此接口公开必须在基础集合更改时引发的CollectionChanged事件。

WPF提供了ObservableCollection <(Of <(T>)>)类,它是一个公开INotifyCollectionChanged接口的数据集合的内置实现。 有关示例,请参见如何:创建和绑定到ObservableCollection。

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

在实现自己的集合之前,请考虑使用ObservableCollection <(Of <(T>)>)或其中一个现有集合类,例如List <(Of <(T>)>),Collection <(Of <(T>) >)和BindingList <(Of <(T>)>)等等。

如果您有高级方案并且想要实现自己的集合,请考虑使用IList,它提供可以通过索引单独访问的非generics对象集合,并提供最佳性能。

听我说问题出在Collection本身,而不是它的枚举器。