Greg Beech's Website

Why doesn't System.Linq.Enumerable have a ForEach extension method?

I don't know for sure, but my guess is because there are at least two implementations with different semantics:

// immediate execution
public static IEnumerable<T> ForEach<T>(this IEnumerable<T> source, Action<T> action)
{
    foreach (var item in source)
    {
        action(item);
    }

    return source;
}

// deferred execution
public static IEnumerable<T> ForEach<T>(this IEnumerable<T> source, Action<T> action)
{
    foreach (var item in source)
    {
        action(item);
        yield return item;
    }
} 

Which implementation is correct? The first one is correct if you want to force the iteration, the second is correct if you want to add lazy processing to a Linq pipeline. Interestingly, however, both violate the the principle of least astonishment: The first is dissimilar to most chainable Linq constructs because they use deferred execution where possible, and sophisticated consumers may be relying on this; the second does not meet many people's expectation that a construct named foreach should actually iterate over a collection.

If there are multiple conflicting solutions to a problem, where all solutions are equally valid and no solution is ideal, then sometimes the right choice is to implement nothing but provide the facilities to let the consumer easily implement the solution that is correct for them.

For the record, our core library function is as follows, because if you don't return a value then it is obvious that the method must execute immediately. This clarity comes at the expense of not being able to chain the method, but for us it's the right trade-off.

public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
{
    foreach (var item in source)
    {
        action(item);
    }
}

I guess that makes at least three implementations...


Posted Jun 30 2008, 01:08 PM by Greg Beech

Comments

DotNetKicks.com wrote Why System.Linq.Enumerable doesn't have a ForEach extension method
on 06-30-2008 4:14 PM

You've been kicked (a good thing) - Trackback from DotNetKicks.com

Will wrote re: Why doesn't System.Linq.Enumerable have a ForEach extension method?
on 06-30-2008 7:09 PM

I use a variation of this (damnit, I wish I could credit the original source!) that takes one to many Action<T>s

public static void ForEach<T>(this IEnumerable<T> sequence, params Action<T>[] actions)

{

   foreach (T t in sequence)

       for (int i = 0; i < actions.Length; i++)

           actions[i](t);

}

This becomes very useful when you are doing high level ops where you want to throw events or log what you're doing.  You can easily throw in a lambda to log a step, another one to fire your OnBefore event, one for the action, one another log for completion, and then your On event.  All in one method call, and the syntax is nice, clean and understandable.

wekempf wrote re: Why doesn't System.Linq.Enumerable have a ForEach extension method?
on 08-25-2008 9:07 PM

I commented on Glenn Block's site, but I'll say it here as well.  Not all LINQ operations are chainable or deferred.  Average and Sum are neither.  I would not expect to be able to chain ForEach, nor would I expect it to be deferred.  I doubt any of this is the reason ForEach is missing in action.

Not all LINQ operations are chainable or deferred, however (as I actually said in the post) all operations that are chainable are also deferred. It might appear that this does not hold true for operations which explicitly convert the sequence to another enumerable type (e.g. ToArray, ToList), though here you aren't really chaining to the original query, you're beginning a new chain from a new object, so it actually does still hold.

Arguably, then, if you make ForEach chainable you should also make it deferred, and if it is not chainable then it should execute immediately (as you can see from the end of the post, the latter is the choice we went with for our core library). The problem is that this is less useful than the one that executes immediately but that can be chained. As I said, none of the methods are ideal, and you might want different semantics in different situations.

I'd be interested to know what you think the reasons are for ForEach being omitted? It certainly isn't laziness, or that they simply forgot to implement it, because this type of method is ubiquitous in functional libraries (including F#, Ruby, etc.) which means that they did leave it out on purpose.

wekempf wrote re: Why doesn't System.Linq.Enumerable have a ForEach extension method?
on 08-26-2008 3:25 PM

I guess I'm looking at this from the perspective of functional programming.  The ForEach concept isn't chainable.  In functional languages we're generally interested in only two chainable operations, Map and Filter, both of which exist in LINQ in the forms of Select and Where.  Then we're interested in Reduce, which maps to Aggregate in LINQ.  All other operations (include ForEach/Each/Loop) can be implemented on those operations alone.  For instance, to print out the values you could do this:

list.Select(x => { Console.WriteLine(x); return x; });

However, since we're not altering the list, it's preferable to use a function that codifies this, so we have various loop/for/while functions.  Since these loops don't modify the list, they don't return anything, and thus are not chainable.  ForEach in LINQ should return void, and by necessity should not be deferred.  No developer that's worked with a functional language will find this surprising... though they might take issue with the names chosen for LINQ. ;)

I certainly agree with your assessment that the ForEach loop concept isn't chainable, and should be immediately executed and return void. This is how List.iter works in F#, how each works in Ruby, and indeed is what I implemented in our core library. I'd be happy enough if this is what was implemented in the base class library (or the one that takes a param array as in one of the above comments).

But it's also a valid concept in an impure language to want to add some lazy processing for each item in a pipeline (e.g. to log the item details) and in this case the ForEach method name would also seem appropriate. All I was trying to get at is that ForEach could mean different things to different people, and it isn't immediately obvious from the method name what that meaning should be.

Maybe the best solution of all is to call the immediately executed non chainable version something like Iterate instead of ForEach, which makes it absolutely obvious what it does.

marcelo wrote re: Why doesn't System.Linq.Enumerable have a ForEach extension method?
on 09-04-2008 4:02 AM

If you don't need to chain, or, for that matter, return any kind of result at all, then what's wrong with just using the foreach statement?

There's nothing inherently wrong with a foreach loop (let's face it, we've been using them quite happily for years) but if you only want to do a simple thing to each item then the ForEach extension method can be more terse to write. For example, if you wanted to print the contents of a collection to the console then the foreach loop would look like this:

foreach (var item in collection)
{
    Console.WriteLine(item)
}

Whereas using the ForEach method it would just be:

collection.ForEach(Console.WriteLine)

If the body is longer then the extension method is no more terse, however there are other potential benefits. For example when the ParallelFx framework is released then any loops using the extension method that could be executed in parallel can be done simply by adding a call to AsParallel before the ForEach call, whereas any using the imperative foreach syntax would not be able to run in parallel unless they were re-written.

Mainly though, I prefer it because it's moving to a more functional style where you declare what you want to do rather than how you achieve it, which I think makes code clearer.

Add a Comment

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

Enter the numbers above:
Copyright (C) Greg Beech. All rights reserved.
Powered by Community Server (Non-Commercial Edition), by Telligent Systems