Not all IEnumerable<T> are equal

Implementing IEnumerable<T> can turn out to be tricky in certain cases. Consider the following code snippet


namespace Test
{
class Program
{
static void Main(string[] args)
{
Consume(new List<string>() { "a", "b", "c" });
}

static void Consume<T>(IEnumerable<T> stream)
{
T t1 = stream.First();
T t2 = stream.First();

Console.WriteLine(t1.Equals(t2));
}
}
}

 


As you’d expect, it prints true. Each First() call results in a call to stream.GetEnumerator(), and each such enumerator returns elements from the beginning of the list, so calling First() twice returns the same (first) element. All good so far.


Here’s a tiny class.


        class StringGenerator
{
int index;

public string GetNext()
{
return (index++).ToString();
}
}

As you can see, it generates whole numbers as strings. Not very convenient to use though, wouldn’t it be great if we can wrap it and make it enumerable?


        static IEnumerable<string> ConvertToEnumerable(StringGenerator g)
{
string item = null;

while ((item = g.GetNext()) != null)
yield return item;
}


ConvertToEnumerable simply loops over the list of items generated and makes use of yield return to make it enumerable.


Great, now what does


        Consume(ConvertToEnumerable(new StringGenerator()));

print?


It prints false.


False? FALSE? Can you figure out the reason?


If you’ve read Raymond’s posts on implementation of iterators, you should have figured it out by now. The crux of the problem is that all enumerators returned share the same instance of StringGenerator.  Calling First() twice results in two calls to GetNext() on the same StringGenerator instance, and the values returned will obviously be different. To verify that, try creating the StringGenerator instance inside the ConvertToEnumerable function – it will print true now.


This bit me when I wrote code that parsed stuff out of an IEnumerable<string> instance. The actual program read text from a file, so I had a ConvertToEnumerable routine just like the one above, except that it took TextReader as the parameter. The Consume method passed the constructed IEnumerable<T> instance to various methods (say Method1 and Method2), with the assumption that whatever Method1 read off the stream won’t be read again by Method2.


As we saw just now, this works if Consume is passed an IEnumerable<T> constructed like the StringGenerator case. It fails badly if a List<string> is passed instead. Because I wanted the “read elements off the stream” behavior, I called GetEnumerator() once for the passed IEnumerable<T> and then changed the methods called by Consume to take that IEnumerator<T> instead of IEnumerable<T>. That made the code work correctly for both cases.


Moral : Make sure you understand the implications when yield returning items off a shared item source.