Why won’t my iterator throw?

I ran into a spot of confusion last night doing some TDD on a method that returns an IEnumerable<T>. I was using multiple yield return statements in it which made the method an iterator and not just a normal method.

Even though I know how iterators work, I don’t use them enough to remember their idiosyncrasies. The main one being that it’s easy to “invoke” them without actually executing any of the code inside them!

To illustrate this using a highly contrived example, imagine you wrote the following test:

[Test]
public void It_throws_when_you_pass_in_null()
{
    Assert.Throws<ArgumentNullException>(
        () => MyObject.MyMethod(null));
}

And then implemented the method it tests it like so:

public static IEnumerable<object> MyMethod(object arg)
{
    if (arg == null)
        throw new ArgumentNullException("arg");

    yield return "whatever";
}

Surprise! Your test fails.

Why? Because the code in your iterator doesn’t start executing until you invoke GetEnumerator on its return value then invoke MoveNext on that.

One really quick way to force these method calls to happen is to use the ToArray extension method:

[Test]
public void It_throws_when_you_pass_in_null()
{
    Assert.Throws<ArgumentNullException>(
        () => MyObject.MyMethod(null).ToArray());
}

ToList or manually iterating over the result with foreach would work just as well.

A better way to fix this is to change your implementation so that it uses two methods:

public static IEnumerable<object> MyMethod(object arg)
{
    if (arg == null)
        throw new ArgumentNullException("arg");

    return MyMethodHelper(arg);
}

private static IEnumerable<object> MyMethodHelper(object arg)
{
    yield return "whatever";
}

Written this way, MyMethod isn’t an iterator anymore. It’s just a plain old method that gets executed the way you’d expect it to. MyMethodHelper becomes the iterator. Its code won’t get executed until you start calling MoveNext, but that’s OK because the validation code you care about already ran.

Problem solved, right? Unfortunately, this wasn’t quite my exact problem.

My method was actually throwing after it did its argument checking and while it was yielding values. There’s really no solution (that I know of) to handle this without invoking MoveNext (or something else that will invoke it for you like ToArray) until whatever condition throws your exception is triggered.

Adding ToArray to my test wasn’t a big deal, but it took me a bit of time to figure out why it was failing. I was actually setting breakpoints in my method, running the test in the debugger, and tripping out when my breakpoints weren’t hitting.

Maybe going through the trouble of writing this post will save me 10 minutes next time I write an iterator.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>