我有一个简单的GetEnumerator
用法。
private ConcurrentQueue<string> queue = new ConcurrentQueue<string>();
public IEnumerator GetEnumerator()
{
return queue.GetEnumerator();
}
我想更新这个类之外的队列。
所以,我在做:
var list = _queue.GetEnumerator();
while (list.MoveNext())
{
list.Current as string = "aaa";
}
**GetEnumerator()
是返回队列的副本,还是迭代原始值?**所以在更新时,我更新原始值?
谢谢你:)
4条答案
按热度按时间5rgfhyps1#
这取决于确切的底层实现。
据我所知,大多数内置的dotnet容器使用当前数据,而不是快照。
如果在迭代集合时修改集合,则很可能会遇到异常--这正是为了防止这个问题。
ConcurrentQueue<T>
的情况并非如此,因为GetEnumerator
方法返回队列内容的快照(从.Net 4.6 - Docs开始)IEnumerator接口的
Current
属性上没有set
,因此不能以这种方式修改集合(Docs)nwlls2ji2#
在迭代时修改集合(添加、删除、替换元素)通常是有风险的,因为人们不应该知道迭代器是如何实现的。
为了在此基础上添加,创建一个队列以获得第一个元素/在末尾添加元素,但在任何情况下都不允许替换“中间”的元素。
以下是两种可行的方法:
方法1 -使用更新的元素创建新队列
迭代原始队列并在进程中重新创建一个新集合。
通过使用linq
.Select
并将结果IEnumerable提供给Queue的构造函数,可以自然地一次完成:注意,可能会消耗资源。当然,其他实现也是可能的,特别是当你的集合很大的时候。
方法2 -可变元素的集合
您可以使用 Package 器类来启用所存储对象的变异:
然后创建一个
private ConcurrentQueue<MyObject> queue = new ConcurrentQueue<MyObject>();
。现在,您可以改变元素,而不必更改集合本身中的任何引用:
在上面的代码中,容器存储的引用从未改变,但是它们的 * 内部状态 * 改变了。
在问题代码中,你实际上是试图用另一个对象来改变一个对象(字符串),这在队列的情况下是不清楚的,并且不能通过只读的
.Current
来完成,对于一些容器甚至应该禁止。icnyk63a3#
下面是一些测试代码,看看我是否可以在
ConcurrentQueue<string>
迭代时对其进行修改。它成功运行并生成
abcabcx
。但是,如果我们将集合更改为标准的
List<string>
,则会失败。实现方法如下:
它在引发
InvalidOperationException
之前生成ab
。qv7cva1a4#
对于
ConcurrentQueue
,则为specifically addressed by the documentation:枚举表示队列内容的即时快照。它不反映调用GetEnumerator后对集合的任何更新。枚举数可安全地同时用于读取和写入队列。
所以答案是:它的行为就像它返回了一个副本。(它实际上并没有创建副本,但效果就像它是一个副本一样--即在枚举原始集合的同时改变它不会改变枚举产生的项。)
其他类型不能保证此行为-例如,如果列表在枚举期间被修改,则尝试枚举
List<T>
将失败。