简体   繁体   English

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

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

I have a simple GetEnumerator usage.我有一个简单的GetEnumerator用法。

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

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

I want to update the queue outside of this class.我想更新这个 class 之外的队列。

So, I'm doing:所以,我在做:

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

Does the GetEnumerator() returns a copy of the queue, or iterated the original value? GetEnumerator()返回队列的副本,还是迭代原始值? So while updating, I update the original?所以在更新的时候,我更新原来的?

Thank you:)谢谢:)

It depends on the exact underlying implementation.这取决于确切的底层实现。

As far as I remember, most of the built in do.net containers use the current data, and not a snapshot.据我所知,大多数内置的 do.net 容器都使用当前数据,而不是快照。

You will likely get an exception if you modify a collection while iterating over it -- this is to protect against exactly this issue.如果您在迭代集合时修改集合,您可能会遇到异常——这是为了防止出现此问题。

This is not the case for ConcurrentQueue<T> , as the GetEnumerator method returns a snapshot of the contents of the queue (as of.Net 4.6 - Docs )不是ConcurrentQueue<T>的情况,因为GetEnumerator方法返回队列内容的快照(自 .Net 4.6 - Docs起)

The IEnumerator interface does not have a set on the Current property, so you cannot modify the collection this way ( Docs ) IEnumerator 接口在Current属性上没有set ,因此您不能以这种方式修改集合( 文档

Modifying a collection (add, remove, replace elements) when iterating is in general risky, as one should not know how the iterator is implemented.在迭代时修改集合(添加、删除、替换元素)通常是有风险的,因为人们不应该知道迭代器是如何实现的。

To add on this, a queue is made to get first element / adding element at the end, but in any case would not allow replacing an element "in the middle".除此之外,还创建了一个队列以获取第一个元素/在末尾添加元素,但无论如何都不允许替换“中间”的元素。

Here are two approaches that could work:以下是两种可行的方法:

Approach 1 - Create a new queue with updated elements方法 1 - 创建一个包含更新元素的新队列

Iterate over the original queue and recreate a new collection in the process.迭代原始队列并在此过程中重新创建一个新集合。

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

This is naturally done in one go by using linq .Select and feed the constructor of Queue with the result IEnumerable:这自然是通过使用 linq .Select在一个 go 中完成的,并将结果 IEnumerable 提供给 Queue 的构造函数:

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

Beware, could be resource consuming.当心,可能会消耗资源。 Of course, other implementations are possible, especially if your collection is large.当然,其他实现也是可能的,尤其是当您的收藏很大时。

Approach 2 - Collection of mutable elements方法 2 - 可变元素的集合

You could use a wrapper class to enable mutation of objects stored:您可以使用包装器 class 来启用存储对象的突变:

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

Then you create a private ConcurrentQueue<MyObject> queue = new ConcurrentQueue<MyObject>();然后你创建一个private ConcurrentQueue<MyObject> queue = new ConcurrentQueue<MyObject>(); instead.反而。

And now you can mutate the elements, without having to change any reference in the collection itself:现在您可以改变元素,而无需更改集合本身中的任何引用:

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

In the code above, the references stored by the container have never changed.在上面的代码中,容器存储的引用从未改变。 Their internal state have changed, though.不过,他们的内部 state已经改变了。

In the question code, you were actually trying to change an object (string) by another object, which is not clear in the case of queue, and cannot be done through .Current which is readonly.在问题代码中,您实际上试图将一个 object(字符串)更改为另一个 object,这在队列的情况下并不清楚,并且不能通过只读的.Current来完成。 And for some containers it should even be forbidden.对于某些容器,它甚至应该被禁止。

Here's some test code to see if I can modify the ConcurrentQueue<string> while it is iterating.下面是一些测试代码,看看我是否可以在迭代时修改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);
}

That runs successfully and produces abcabcx .成功运行并生成abcabcx

However, if we change the collection to a standard List<string> then it fails.但是,如果我们将集合更改为标准的List<string>那么它就会失败。

Here's the implementation:这是实现:

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);
}

That produces ab before throwing an InvalidOperationException .在抛出InvalidOperationException之前会产生ab

For ConcurrentQueue this is specifically addressed by the documentation :对于ConcurrentQueue ,这是由文档专门解决的

The enumeration represents a moment-in-time snapshot of the contents of the queue.枚举表示队列内容的即时快照。 It does not reflect any updates to the collection after GetEnumerator was called.它不反映调用 GetEnumerator 后对集合的任何更新。 The enumerator is safe to use concurrently with reads from and writes to the queue.枚举器可以安全地与队列的读取和写入并发使用。

So the answer is: It acts as if it returns a copy.所以答案是:它就像返回一个副本一样。 (It doesn't actually make a copy, but the effect is as if it was a copy - ie changing the original collection while enumerating it will not change the items produced by the enumeration.) (它实际上并没有制作副本,但效果就好像它是一个副本 - 即在枚举它的同时更改原始集合不会更改枚举产生的项目。)

This behaviour is NOT guaranteed for other types - for example, attempting to enumerate a List<T> will fail if the list is modified during the enumeration.对于其他类型不保证此行为 - 例如,如果列表在枚举期间被修改,则尝试枚举List<T>将失败。

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

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