Reputation: 1028
List enumerators not working in case when they are put in a list. For example in case of two lists (two enumerators)
public void test()
{
var firstList = new List<int>() { 1, 2, 3 };
var secondList = new List<int>() { 4, 5, 6 };
var lists = new List<List<int>>();
lists.Add(firstList);
lists.Add(secondList);
// Not working
var iterators = lists.Select(x => x.GetEnumerator()).ToList();
iterators.ForEach(x => x.MoveNext());
iterators.ForEach(x => Console.WriteLine(x.Current));
// Working
var firstIterator = iterators[0];
var secondIterator = iterators[1];
firstIterator.MoveNext(); secondIterator.MoveNext();
Console.WriteLine(firstIterator.Current);
Console.WriteLine(secondIterator.Current);
}
the first part is not working and it prints 0 0
, while the second part is working and it prints 1 4
.
I don't understand what is mistake with the first part and how it can be solved.
Upvotes: 1
Views: 1130
Reputation: 205629
This is because the List<T>.GetEnumerator
method returns a List<T>.Enumerator
mutable struct.
When you assign it to a variable and call MoveNext
and Current
, it works. But when passed to delegate, it is passed by value, hence the delegate receives a copy so calling MoveNext
has no effect of the original struct
Current
.
Just one of the side effects of the mutable structs.
If you change
x => x.GetEnumerator()
to
x => (IEnumerator<int>)x.GetEnumerator()
or
x => x.AsEnumerable().GetEnumerator()
both snippets will work because now the iterators
will contain a boxed references of the returned structs.
Upvotes: 6
Reputation: 236248
List's enumerator is implemented as a value type (source code):
iterators[0].GetType().IsValueType // true
That means - iterators are passed by value when you are passing them to the methods (i.e. copy of iterator is passed instead of passing a reference to iterator instance). ForEach
method simply executes an action in a loop:
iterators.ForEach(copy => copy.MoveNext());
Action delegate is a method, so you are passing a copy of each iterator to this method. So original iterators remain unchanged. And when you call ForEach
second time, again, copies of original untouched iterators are passed there:
iterators.ForEach(newCopy => Console.WriteLine(newCopy.Current));
But original iterators are in the initial state (before the first item) that is why you see default values of integer type when you read Current
value.
You can either work with single copy of iterator:
iterators.ForEach(copy => { copy.MoveNext(); Console.WriteLine(copy.Current); });
Or box value type to pass it by reference (note that type of iterators
list will change):
var iterators = lists.Select(x => (IEnumerable<int>)x.GetEnumerator()).ToList();
Upvotes: 1
Reputation: 4043
Its because you run 2 separate .ForEach()
in the not working part. Do both actions in 1 foreach (MoveNext()
, then print). MoveNext
will not be remembered in a .Foreach()
for a good explaination: Go to this answer
This:
iterators.ForEach(x => x.MoveNext());
iterators.ForEach(x => Console.WriteLine(x.Current));
Becomes:
iterators.ForEach(x => { x.MoveNext(); Console.WriteLine(x.Current); });
Upvotes: 1