none
Why doesn't IEnumerable<T> have a .ForEach method?

    Question

  • Before I begin, I sincerely hope a couple of things:

    • I am in the correct forum for this post, and
    • I have not missed any previous articles/answers explaining why this design decision was made. I fear this has been answeredlong ago - have searched much - just can't find anything definite.

    Essentially want to not have to call an intermediate .ToList() on my queries to be able to execute an action for each result using ForEach.

    For example, I have a query for which I want to execute a method for every elemented returned, and gather and log information about any element for which the operation failed:

    var query = 
        from    item
        in        items

        some joins...
        some lets...

        select CallMethodThatRetunsOperationSuccessOrFailureResult( item );



    query.Where( result => result.Failure ).ToList().ForEach( result => LogFailure( result ) );


    That actually works great for me - no problems at all. Really just want to not have to use an intervening .ToList(), as in:

    query.Where( result => result.Failure ).ForEach( result => LogFailure( result ) );


    I've come across this link so I know I can create either a .ForEach() or .Apply() extension method to do this:

    http://www.codethinked.com/post/2008/05/IEnumerable-ForEach-extension-method.aspx

    Would prefer to use a Microsoft implementation, however. If it is indeed as easy as it seems to add such an extension method, why wouldn't it already have been included by Microsoft? In the end, having to use an intervening .ToList() to get to .ForEach() really isn't that big of a deal. Just want to try to get an idea of what design decisions may have led to not including something that seems obvious to include before I charge into rolling out an extension method. Suspect it may come down to some subtelties regarding deferred execution but not sure.

    Thanks.

    • Edited by X45T Wednesday, June 25, 2008 7:19 PM Forgot to remove some unneeded code lines before initial post.
    • Moved by Peter RitchieMVP, Moderator Wednesday, June 25, 2008 7:48 PM Question relates to types in the BCL
    Wednesday, June 25, 2008 7:18 PM

Answers

  • I'd comment that you're dealing with two thing here really, which you've already alluded to:

    1) With the linq query, it is deferred until some enumeration is performed. This means that you really don't have any collection until the ToList is called. I am pretty sure that the Where() method wouldn't generate a list for you to call ForEach on, as it is another linq statement in reality.

    There wouldn't be any different in writing it like this (can't test this psudocode right now):

        var query = 
            from    item
            in        items

            some joins...
            some lets...

            select CallMethodThatRetunsOperationSuccessOrFailureResult( item );

        var query2 =
             from q in query
             where result => result.Failure
             select q;

        query2.ToList().ForEach( result => LogFailure( result ) );

    2) IEnumerable<T> is an interface, it can't define how a method would work. It could define that each IEnumerable<T> implments a ForEach method, but what would that look like for all enumerable types? Remember that to implement the interface you must provide an implementation of each method it defines. Maybe for whatever reason, ForEach method would not work for every possible implementor of IEnumerable<T>.

    Granted, in the example extension method, foreach is called, which is an enumeration over the collection, which will process through the linq query. You would have to force that every implementation of ForEach to use enumeration through the values, which is a bit beyond the scope of what an interface should be. Hypothetically, there is no reason why I could not create a IEnumerable<T> implementation that for some obscure reason wouldn't use enumeration on a ForEach method call, what would happen with the deferred linq query then, or anything else that would rely on the enumeration? (Whether that is practical or proper is another discussion, but the interface definition should be the thing to restrict that)


    Also, what is wrong with making the log call like:
    foreach( var result in query ) { LogFailure( result ); }

    It may not use the nice and fancy lamda expressions or functions like ForEach(), but it certainly does the same work. Is there another reason why you need the ForEach method, Action<T> delegate, and lambda expression. You can certainly still use delegates and lambda expressions with the regular foreach statement if you needed to.


    That is at least my opinion on the issue, anyone else have thoughts on the issue?

    Eric Steinbrenner
    • Marked as answer by X45T Thursday, June 26, 2008 8:37 PM
    Wednesday, June 25, 2008 9:12 PM
  • I'd assume the reason it isn't implemented in the framework is because there are two possible behaviours, both of which are equally valid:

    // this version uses immediate execution 
    public static IEnumerable<T> ForEach<T>(this IEnumerable<T> source, Action<T> action) 
        foreach (var item in source) 
        { 
            action(item); 
        } 
     
        return source; 
     
    // this version uses 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 is correct? The first one is correct if you want to force the iteration, the second is correct if you simply want to add some processing to a Linq pipeline. If there are multiple equally reasonable solutions to a problem, then sometimes the correct solution is to provide the facilities to let the consumer implement the one that is correct for them. Choose your weapon from the above.

    http://gregbeech.com/blogs/tech
    • Marked as answer by X45T Thursday, June 26, 2008 8:37 PM
    Thursday, June 26, 2008 12:57 AM

All replies

  • I'd comment that you're dealing with two thing here really, which you've already alluded to:

    1) With the linq query, it is deferred until some enumeration is performed. This means that you really don't have any collection until the ToList is called. I am pretty sure that the Where() method wouldn't generate a list for you to call ForEach on, as it is another linq statement in reality.

    There wouldn't be any different in writing it like this (can't test this psudocode right now):

        var query = 
            from    item
            in        items

            some joins...
            some lets...

            select CallMethodThatRetunsOperationSuccessOrFailureResult( item );

        var query2 =
             from q in query
             where result => result.Failure
             select q;

        query2.ToList().ForEach( result => LogFailure( result ) );

    2) IEnumerable<T> is an interface, it can't define how a method would work. It could define that each IEnumerable<T> implments a ForEach method, but what would that look like for all enumerable types? Remember that to implement the interface you must provide an implementation of each method it defines. Maybe for whatever reason, ForEach method would not work for every possible implementor of IEnumerable<T>.

    Granted, in the example extension method, foreach is called, which is an enumeration over the collection, which will process through the linq query. You would have to force that every implementation of ForEach to use enumeration through the values, which is a bit beyond the scope of what an interface should be. Hypothetically, there is no reason why I could not create a IEnumerable<T> implementation that for some obscure reason wouldn't use enumeration on a ForEach method call, what would happen with the deferred linq query then, or anything else that would rely on the enumeration? (Whether that is practical or proper is another discussion, but the interface definition should be the thing to restrict that)


    Also, what is wrong with making the log call like:
    foreach( var result in query ) { LogFailure( result ); }

    It may not use the nice and fancy lamda expressions or functions like ForEach(), but it certainly does the same work. Is there another reason why you need the ForEach method, Action<T> delegate, and lambda expression. You can certainly still use delegates and lambda expressions with the regular foreach statement if you needed to.


    That is at least my opinion on the issue, anyone else have thoughts on the issue?

    Eric Steinbrenner
    • Marked as answer by X45T Thursday, June 26, 2008 8:37 PM
    Wednesday, June 25, 2008 9:12 PM
  • I'd assume the reason it isn't implemented in the framework is because there are two possible behaviours, both of which are equally valid:

    // this version uses immediate execution 
    public static IEnumerable<T> ForEach<T>(this IEnumerable<T> source, Action<T> action) 
        foreach (var item in source) 
        { 
            action(item); 
        } 
     
        return source; 
     
    // this version uses 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 is correct? The first one is correct if you want to force the iteration, the second is correct if you simply want to add some processing to a Linq pipeline. If there are multiple equally reasonable solutions to a problem, then sometimes the correct solution is to provide the facilities to let the consumer implement the one that is correct for them. Choose your weapon from the above.

    http://gregbeech.com/blogs/tech
    • Marked as answer by X45T Thursday, June 26, 2008 8:37 PM
    Thursday, June 26, 2008 12:57 AM
  • Thanks much for the information - this is exactly the kind of information I was looking for, namely, what are the design ramifications of including or making my own .ForEach.

    Some follow up comments to each post respectively:

    IsshouFuuraibou:

    - Had missed one obvious thing here that you pointed out - .Where returns IEnumerable<T>, as is expected for all 'composable' linq expressions. To introduce some methods returning List<T> and some not just for convenience in forcing execution would clearly be unwanted.

    - Complications of implementing IEnumerable<T>.ForEach for all/varied possible scenarios - good point.

    - Using fancy lamdas just because you can instead of regular foreach - point taken. Liked the fact that .Where could easily handle what would otherwise be an extra test inside the foreach for only failures, but in any case good to keep in mind for purposes of avoiding uneeded overuse.

    Greg Beech:

    - In addition to forgetting to consider the usage of yield return in general, would not have initially expected there would even have been a difference in effect on deferred execution. Great examples - now I know.
    • Marked as answer by X45T Thursday, June 26, 2008 8:37 PM
    • Unmarked as answer by X45T Thursday, June 26, 2008 8:37 PM
    Thursday, June 26, 2008 8:36 PM