繁体   English   中英

c# 中的 GetEnumerator() 是否返回副本或迭代原始来源?

[英]Does the GetEnumerator() in c# return a copy or the iterates the original source?

我有一个简单的GetEnumerator用法。

private ConcurrentQueue<string> queue = new ConcurrentQueue<string>();

public IEnumerator GetEnumerator()
{
    return queue.GetEnumerator();
}

我想更新这个 class 之外的队列。

所以,我在做:

var list = _queue.GetEnumerator();
while (list.MoveNext())
{
    list.Current as string = "aaa";
}

GetEnumerator()返回队列的副本,还是迭代原始值? 所以在更新的时候,我更新原来的?

谢谢:)

这取决于确切的底层实现。

据我所知,大多数内置的 do.net 容器都使用当前数据,而不是快照。

如果您在迭代集合时修改集合,您可能会遇到异常——这是为了防止出现此问题。

不是ConcurrentQueue<T>的情况,因为GetEnumerator方法返回队列内容的快照(自 .Net 4.6 - Docs起)

IEnumerator 接口在Current属性上没有set ,因此您不能以这种方式修改集合( 文档

在迭代时修改集合(添加、删除、替换元素)通常是有风险的,因为人们不应该知道迭代器是如何实现的。

除此之外,还创建了一个队列以获取第一个元素/在末尾添加元素,但无论如何都不允许替换“中间”的元素。

以下是两种可行的方法:

方法 1 - 创建一个包含更新元素的新队列

迭代原始队列并在此过程中重新创建一个新集合。

var newQueueUpdated = new ConcurrentQueue<string>();
var iterator = _queue.GetEnumerator();
while (iterator.MoveNext())
{
    newQueueUpdated.Add("aaa");
}
_queue = newQueueUpdated;

这自然是通过使用 linq .Select在一个 go 中完成的,并将结果 IEnumerable 提供给 Queue 的构造函数:

_queue  = new ConcurrentQueue<string>(_queue.Select(x => "aaa"));

当心,可能会消耗资源。 当然,其他实现也是可能的,尤其是当您的收藏很大时。

方法 2 - 可变元素的集合

您可以使用包装器 class 来启用存储对象的突变:

public class MyObject
{
    public string Value { get; set; }
}

然后你创建一个private ConcurrentQueue<MyObject> queue = new ConcurrentQueue<MyObject>(); 反而。

现在您可以改变元素,而无需更改集合本身中的任何引用:

var enumerator = _queue.GetEnumerator();
while (enumerator.MoveNext())
{
    enumerator.Current.Value = "aaa";
}

在上面的代码中,容器存储的引用从未改变。 不过,他们的内部 state已经改变了。

在问题代码中,您实际上试图将一个 object(字符串)更改为另一个 object,这在队列的情况下并不清楚,并且不能通过只读的.Current来完成。 对于某些容器,它甚至应该被禁止。

下面是一些测试代码,看看我是否可以在迭代时修改ConcurrentQueue<string>

ConcurrentQueue<string> queue = new ConcurrentQueue<string>(new[] { "a", "b", "c" });

var e = queue.GetEnumerator();

while (e.MoveNext())
{
    Console.Write(e.Current);
    if (e.Current == "b")
    {
        queue.Enqueue("x");
    }
}

e = queue.GetEnumerator(); //e.Reset(); is not supported
while (e.MoveNext())
{
    Console.Write(e.Current);
}

成功运行并生成abcabcx

但是,如果我们将集合更改为标准的List<string>那么它就会失败。

这是实现:

List<string> list = new List<string>(new[] { "a", "b", "c" });

var e = list.GetEnumerator();

while (e.MoveNext())
{
    Console.Write(e.Current);
    if (e.Current == "b")
    {
        list.Add("x");
    }
}

e = list.GetEnumerator();
while (e.MoveNext())
{
    Console.Write(e.Current);
}

在抛出InvalidOperationException之前会产生ab

对于ConcurrentQueue ,这是由文档专门解决的

枚举表示队列内容的即时快照。 它不反映调用 GetEnumerator 后对集合的任何更新。 枚举器可以安全地与队列的读取和写入并发使用。

所以答案是:它就像返回一个副本一样。 (它实际上并没有制作副本,但效果就好像它是一个副本 - 即在枚举它的同时更改原始集合不会更改枚举产生的项目。)

对于其他类型不保证此行为 - 例如,如果列表在枚举期间被修改,则尝试枚举List<T>将失败。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM