Deferred execution gotchas

I was trying to be clever the other day and try to chain assertions on an array:

[Test]
public void This_does_not_work()
{
    var items = new[] {1, 2, 3, 4, 5};
    items
        .Each(x => x.ShouldBeLessThan(0))
        .Each(x => x.ShouldBeGreaterThan(10));
}

I did this by writing an Each extension method that I’ve seen in about a hundred different variations.  However, mine was a special implementation, and special as in it didn’t quite work as intended:

public static IEnumerable<T> Each<T>(this IEnumerable<T> items, Action<T> action)
{
    foreach (var item in items)
    {
        action(item);
        yield return item;
    }
}

Clever, clever, I iterate over the items, performing the action for each item, and finally yielding the item back again (for further processing).  To my surprise, the above test passed (when it should have failed).

After putting in some Debug.WriteLines, I found that none of the actions were ever executed.  This is because of deferred execution.  With deferred execution, the operations aren’t executed until you try and iterate over the results.

You can try and iterate over the results by doing a foreach, or calling another method that does something similar like ToArray().  Here’s the modified test that fails as expected:

[Test]
public void This_fails_as_expected_but_quite_ugly()
{
    var items = new[] {1, 2, 3, 4, 5};
    items
        .Each(x => x.ShouldBeLessThan(0))
        .Each(x => x.ShouldBeGreaterThan(10))
        .ToArray();
}

It works, but now I have to call a ToArray method to force an iteration over the collection.  Ugly, as it obscures the intent of the test with technical details of deferred execution.

I could bypass deferred execution by not using custom iterators:

public static IEnumerable<T> Each<T>(this IEnumerable<T> items, Action<T> action)
{
    foreach (var item in items)
    {
        action(item);
    }

    return items;
}

This works just fine, with no need to get clever with a custom iterator (the yield return business).  If I decided to get even more clever, and I wanted to keep the deferred execution for whatever reason, I can add yet another extension method:

public static void Iterate<T>(this IEnumerable<T> items)
{
    foreach (var item in items)
    {
    }
}

And now I can be more explicit in my tests that I need to iterate the results:

[Test]
public void This_does_work_and_is_not_so_ugly()
{
    var items = new[] {1, 2, 3, 4, 5};
    items
        .Each(x => x.ShouldBeLessThan(0))
        .Each(x => x.ShouldBeGreaterThan(10))
        .Iterate();
}

This test fails as expected, without needing to do a ToArray call that obscures intent.

It turns out that I’m far more likely to do something like a map or reduce than just performing an action on a collection.  I’m doing a lot more Func than Action in real-world code.  Another example that clever is not always simple.

Related Articles:

Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Jimmy Bogard

I'm a technical architect with Headspring in Austin, TX. I focus on DDD, distributed systems, and any other acronym-centric design/architecture/methodology. I created AutoMapper and am a co-author of the ASP.NET MVC in Action books.
This entry was posted in C#, LINQ. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • http://www.peterRitchie.com/blog Peter Ritchie
  • http://colinjack.blogspot.com Colin Jack

    This stuff is so annoying, myself and a colleague just debugged a piece of legacy code that used yield after printing to console and connecting to DB directly and couldn’t work out why we couldn’t step into the method.

    Took us quite a few minutes to work out it was the yield and the fact that we weren’t iterating through the returned IEnumerable that was the problem, very annoying!