当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
给定索引处的值。 这意味着枚举集合并删除“每个”项目实际上将无法按预期工作(即,它不会删除列表中的每个项目)。
考虑这种可能性:
您发布的代码实际上会执行此操作:
- 首先将索引递增为0,这将为您提供“摇滚”的
Current
值。 你删除“摇滚”。 - 现在集合有
["roll", "rain dogs"]
并且你将索引增加到1, 使Current
等于“rain dogs”(不是“roll”) 。 接下来,你删除“雨狗”。 - 现在集合有
["roll"]
,你将索引增加到2(>>Count
); 所以你的普查员认为它已经完成了。
不过,还有其他原因,这是一个有问题的实现。 例如,使用你的代码的人可能无法理解你的枚举器是如何工作的(也不应该 – 实现应该无关紧要),因此没有意识到在foreach
块中调用Remove
的成本会导致IndexOf
的惩罚 – 即,线性搜索 – 每次迭代(请参阅ArrayList.Remove
上的MSDN文档。 ArrayList.Remove
以validation这一点)。
基本上,我得到的是:你不希望能够从foreach
循环中删除项目(再次,除非你设计一些线程安全的东西…… 也许 )。
好的,那么替代方案是什么? 以下几点可以帮助您入门:
- 不要将您的集合设计为允许 – 更不用说期望 – 在枚举中进行修改。 它导致了奇怪的行为,例如我上面提供的例子。
- 相反,如果要提供批量删除function,请考虑
Clear
(删除所有项目)或删除所有项目(删除与指定filter匹配的项目)等方法。 - 这些批量移除方法可以相当容易地实现。
ArrayList
已经有一个Clear
方法,就像你在.NET中可能使用的大多数集合类一样。 否则,如果您的内部集合已编制索引,则删除多个项目的常用方法是使用for
循环从顶部索引枚举,并在需要删除的RemoveAt
上调用RemoveAt
(请注意,这会立即解决两个问题:从顶部,您确保访问集合中的每个项目;此外,通过使用RemoveAt
而不是Remove
,您可以避免重复线性搜索的惩罚。 - 作为补充说明,我强烈建议首先清除非通用集合,例如
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本身,而不是它的枚举器。