Los Techies : Blogs about software and anything tech!

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.

Kick It on DotNetKicks.com
Posted Aug 13 2008, 08:08 AM by bogardj
Filed under: ,

Comments

Peter Ritchie wrote re: Deferred execution gotchas
on 08-13-2008 9:42 AM

Very similar to a post by Greg Young: codebetter.com/.../quick-rant.aspx

Colin Jack wrote re: Deferred execution gotchas
on 08-13-2008 10:05 AM

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!

Dew Drop - August 14, 2008 | Alvin Ashcraft's Morning Dew wrote Dew Drop - August 14, 2008 | Alvin Ashcraft's Morning Dew
on 08-14-2008 8:50 AM

Pingback from  Dew Drop - August 14, 2008 | Alvin Ashcraft's Morning Dew

Add a Comment

(required)  
(optional)
(required)  
Remember Me?

Enter the numbers above:
Copyright Los Techies 2008, 2009. All rights reserved.
Powered by Community Server (Commercial Edition), by Telligent Systems