问题描述
如果我自己实现 IEnumerator
接口的实现,则可以(在 foreach
语句内)添加或从相册列表
中删除项目而不会产生异常。但是,如果 foreach
语句使用 IEnumerator
由 albumsList
提供,然后尝试从 albumsList
添加/删除(在foreach中)项目将会导致以下异常:
If I make my own implementation of IEnumerator
interface, then I am able ( inside foreach
statement )to add or remove items from a albumsList
without generating an exception.But if foreach
statement uses IEnumerator
supplied by albumsList
, then trying to add/delete ( inside the foreach )items from albumsList
will result in 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)我假设代码会引发异常(使用 albumsList IEnumerator
实现 A
时, c $ c>)位于 A
内?
a) I assume code that throws exception ( when using IEnumerator
implementation A
supplied by albumsList
) is located inside A
?
b)如果我希望能够从集合中添加/删除项目(而 foreach
遍历),我是否总是需要提供自己的 IEnumerator
接口实现,还是可以将albumsList设置为允许添加/删除项目?
b) If I want to be able to add/remove items from a collection ( while foreach
is iterating over it), will I always need to provide my own implementation of IEnumerator
interface, or can albumsList be set to allow adding/removing items?
谢谢
推荐答案
通常不建议设计允许您在枚举时修改集合的集合类,除非您打算专门设计某种线程安全的东西,以便做到这一点(例如,从一个线程添加而从另一个线程枚举)。
Generally it's discouraged to design collection classes that allow you to modify the collection while enumerating, unless your intention is to design something thread-safe specifically so that this is possible (e.g., adding from one thread while enumerating from another).
原因很多。
您的 MyEnumerator
类通过增加内部计数器来工作。其 Current
属性在 ArrayList
中公开给定索引处的值。这意味着对集合进行枚举并删除每个项目实际上将无法按预期进行(即,它不会删除列表中的每个项目)。
Your MyEnumerator
class works by incrementing an internal counter. Its Current
property exposes the value at the given index in an ArrayList
. What this means is that enumerating over the collection and removing "each" item will actually not work as expected (i.e., it won't remove every item in the list).
考虑这种可能性:
您发布的代码实际上会这样做:
The code you posted will actually do this:
- 您首先将索引增加到0,这将使
当前
的值为 rock。删除 rock。 - 现在集合中有
[ roll, rain dogs]
,然后将索引增加到1,使Current
等于雨狗(不滚动)。接下来,删除雨狗。 - 现在集合中有
[ roll]
,然后将索引增加到2 (即>Count
);因此,您的枚举器认为它已完成。
- You start by incrementing your index to 0, which gives you a
Current
of "rock." You remove "rock." - Now the collection has
["roll", "rain dogs"]
and you increment your index to 1, makingCurrent
equal to "rain dogs" (NOT "roll"). Next, you remove "rain dogs." - Now the collection has
["roll"]
, and you increment your index to 2 (which is >Count
); so your enumerator thinks it's finished.
尽管如此,还有其他原因。例如,使用您的代码的某人可能不了解您的枚举器的工作方式(也不应该 -实现应该真的无关紧要),因此没有意识到调用 Remove的代价在
会产生 foreach
块中的 IndexOf
的罚款-即线性搜索- -每次迭代(请参见进行验证)。
There are other reasons this is a problematic implementation, though. For instance someone using your code might not understand how your enumerator works (nor should they -- the implementation should really not matter), and therefore not realize that the cost of calling Remove
within a foreach
block incurs the penalty of IndexOf
-- i.e., a linear search -- on every iteration (see the MSDN documentation on ArrayList.Remove
to verify this).
基本上,我的意思是:你不知道t 想要以便能够从 foreach
循环中删除项目(再次,除非您正在设计线程安全的东西... 也许)。
Basically, what I'm getting at is: you don't want to be able to remove items from within a foreach
loop (again, unless you're designing something thread-safe... maybe).
好的,那有什么选择呢?以下是一些让您入门的要点:
OK, so what is the alternative? Here are a few points to get you started:
- 不要设计允许的收藏集-更不用说期待-枚举内的修改。它会导致奇怪的行为,例如我上面提供的示例。
- 相反,如果要提供批量删除功能,请考虑使用
Clear (删除所有所有项目)或
RemoveAll
(删除与指定过滤器匹配的项目)。 - 这些批量移除方法可以轻松实施。
ArrayList
已经具有Clear
方法,就像您在.NET中可能使用的大多数集合类一样。否则,如果内部集合已建立索引,则删除多个项目的常用方法是使用for
循环从顶部索引中枚举并调用RemoveAt
指向需要删除的索引(注意,这可以立即解决两个问题:通过从顶部向后退,可以确保访问集合中的每个项目;此外,可以使用RemoveAt
而不是删除
,可以避免重复线性搜索的代价。) - 作为补充,我会强烈建议一开始就避免使用非通用集合,例如
ArrayList
。使用强类型的通用副本,例如List(Of Album)
(假设您有Album
类-否则,List(Of String)
仍比ArrayList
)更安全。
Don't design your collection to allow -- let alone expect -- modification within an enumeration. It leads to curious behavior such as the example I provided above.
Instead, if you want to provide bulk removal capabilities, consider methods such as
Clear
(to remove all items) orRemoveAll
(to remove items matching a specified filter).These bulk-removal methods can be implemented fairly easily.
ArrayList
already has aClear
method, as do most of the collection classes you might use in .NET. Otherwise, if your internal collection is indexed, a common method to remove multiple items is by enumerating from the top index using afor
loop and callingRemoveAt
on indices where removal is desired (notice this fixes two problems at once: by going backwards from the top, you ensure accessing each item in the collection; moreover, by usingRemoveAt
instead ofRemove
, you avoid the penalty of repeated linear searches).As an added note, I would strongly encourage steering clear of non-generic collections such as
ArrayList
to begin with. Go with strongly typed, generic counterparts such asList(Of Album)
instead (assuming you had anAlbum
class -- otherwise,List(Of String)
is still more typesafe thanArrayList
).
这篇关于在foreach对其进行迭代时,无法从集合中添加/删除项目的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!