Cook Computing

List<T> Enumerator Gotcha

June 4, 2009 Written by Charles Cook

I find I am using sequences and iterators much more these days because of the influence of Linq. Even when using manipulating arrays this often results in more robust code because you don't have to worry about boundary conditions with indices; and it is easier to pass around an iterator rather than an array and a reference to the current position within the array, or so I thought until I came across an issue with List<T>.GetEnumerator() which not is immediately obvious and which may apply to other collection classes.

I had refactored some code into a separate function, passing in the instance of List<string>.Enumerator I was using. This code illustrates the problem:


using System;
using System.Collections.Generic;

class Program
{
  static void Main(string[] args)
  {
    var list = new List<string>() { "text" };
    var iterator = list.GetEnumerator();
    iterator.MoveNext();
    Console.Write("{0} ", iterator.Current ?? "null");
    Foo(iterator);
    Console.Write("{0}", iterator.Current ?? "null");
  }

  private static void Foo(List<string>.Enumerator iterator)
  {
    iterator.MoveNext();
    Console.Write("{0} ", iterator.Current ?? "null");
  }
}

I expected that after the return from the function the state of the iterator would reflect the call to MoveNext() made in the function, i.e. in this example the output would be "text null null". But the output is actually "text null text". This seemed inexplicable until I discovered that the Enumerator<T> type returned by GetEnumerator() is a struct which means a copy of the struct is passed to the function. Presumably the position of the iterator is held in a value type, maybe an index into an array, which means that any changes to the position will not be reproduced in the original instance of the struct in the calling function.

The solution is to box the struct by casting it to an interface:


using System;
using System.Collections.Generic;

class Program
{
  static void Main(string[] args)
  {
    // ...
    var iterator = list.GetEnumerator() as IEnumerator<string>;
    // ...	
  }

  private static void Foo(IEnumerator<string> iterator)
  {
    // ...
  }
}

This ensures that the called function has access to the same instance of the struct as the calling function.