Home > Enterprise >  Does the GetEnumerator() in c# return a copy or the iterates the original source?
Does the GetEnumerator() in c# return a copy or the iterates the original source?

Time:01-04

I have a simple GetEnumerator usage.

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

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

I want to update the queue outside of this 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? So while updating, I update the original?

Thank you :)

CodePudding user response:

It depends on the exact underlying implementation.

As far as I remember, most of the built in dotnet containers use the current data, and not a snapshot.

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 it is a read-only collection (as of .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)

CodePudding user response:

Here's some test code to see if I can modify the ConcurrentQueue<string> while it is iterating.

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.

However, if we change the collection to a standard List<string> then it fails.

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.

CodePudding user response:

Standard containers in .NET are indeed iterating on "themselves", no copy is done when using GetEnumerator.

Modifying a collection (add, remove, replace elements) is in general a very risky idea, as you should not know how the iterator is implemented.

A queue would allow 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

Iterate over the original queue and recreate a new collection in the process.

This is naturally done in one go by using linq .Select and feed the constructor of Queue with the result IEnumerable:

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

// now, if you want, you can do _queue = newQueueUpdated.

Beware, could be resource consuming. Of course, other implementations are possible, especially if your collection is large.

Approach 2 - Collection of mutable elements

Instead of directly manipulating string in the collection, you could use wrapper class to enable mutation of objects stored:

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

Then you create a 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 list = _queue.GetEnumerator();
while (list.MoveNext())
{
    list.Current.Value = "aaa";
}

In the code above, the references stored by the container have never changed. Their internal state have changed, though.

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. And for some containers it should even be forbidden.

  • Related