Dejan
Dejan

Reputation: 1028

Iterate over list of iterators linq

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

Answers (3)

Ivan Stoev
Ivan Stoev

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

Sergey Berezovskiy
Sergey Berezovskiy

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

EpicKip
EpicKip

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

Related Questions