.Net - When is List<T>.ForEach prefered over a standard foreach loop?
Asked Answered
A

2

4

The generic list class has a .ForEach(Action<T> action) method. Now i've done some simple timings of how they both perform and it seems that the generic ForEach is the poorer performer. The (Snippet Compiler Friendly) code is below -

public static class timer{
    public static long foreachloop = 0;
    public static long Gforeachloop = 0;}

public class something{
    public List<string> myStrings = new List<string>();

    public something()
    {
        for(int i = 1; i<=5000000;i++)
        {
            myStrings.Add(i.ToString());
        }
    }}

public class cls1{
    private static List<string> Strings = new List<string>();
    private static List<string> OtherStrings = new List<string>();

    public static void RunSnippet()
    {
        something s = new something();

        Stopwatch watch = new Stopwatch();
        watch.Start();
        foreach(string x in s.myStrings)
        {
            Strings.Add(x);
        }
        watch.Stop();
        timer.foreachloop = watch.ElapsedMilliseconds;

        watch.Reset();
        watch.Start();

        s.myStrings.ForEach(delegate(string n){OtherStrings.Add(n);});

        s.myStrings.Clear();

        watch.Stop();
        timer.Gforeachloop = watch.ElapsedMilliseconds;

        WL("FOREACH-"+timer.foreachloop + ",Count = " + Strings.Count);
        WL("GFOREACH-"+timer.Gforeachloop + ",Count = " + OtherStrings.Count);
    }

    #region Helper methods

    public static void Main()
    {
        try
        {
            RunSnippet();
        }
        catch (Exception e)
        {
            string error = string.Format("---\nThe following error occurred while executing the snippet:\n{0}\n---", e.ToString());
            Console.WriteLine(error);
        }
        finally
        {
            Console.Write("Press any key to continue...");
            Console.ReadKey();
        }
    }

    private static void WL(object text, params object[] args)
    {
        Console.WriteLine(text.ToString(), args);   
    }

    private static void RL()
    {
        Console.ReadLine(); 
    }

    private static void Break() 
    {
        System.Diagnostics.Debugger.Break();
    }

    #endregion
}

FOREACH comes out at 177ms and GFOREACH at 707ms.

Now I'm guessing there's a good reason for using it but i just can't think of one. Clearly performance isn't the reason so the question is when would it be the best option?

Thanks in advance.

Archimedes answered 23/7, 2009 at 16:35 Comment(1)
Related/Dupe? #1172972Niblick
W
7

This blog post from Eric Lippert gives the background:

http://blogs.msdn.com/ericlippert/archive/2009/05/18/foreach-vs-foreach.aspx

He's talking about the common suggestion of an extension method to do the same thing for IEnumerable<T>, but the philosophical objection applies to List<T>.ForEach as well.

This suggests that maybe that method was never such a good idea, although it looks "cool". It's clearer to just use foreach.

I've suggested that such methods can be thought of as a fix for the classic closure-over-loop-variable bug.

But in practice I've just got better at spotting such bugs.

Wrangle answered 23/7, 2009 at 16:42 Comment(7)
He primarily talks about .ForEach on IEnumerable<T> (where you chain and compose stuff), not on List<T> where you want to perform a method on each object in the list in a single line.Rosinski
But there is no widely agreed sense in which ForEach on IEnumerable<T> would be composable. The obvious, consistent implementation is to mimic List<T>.ForEach and return void, exactly as Eric has in his blog post example. And so the same philosophical objections apply to both.Wrangle
And regarding the loop variable closure problem, I just wish they'd made anonymous delegates and lambdas "smart" about loop variables, capturing a copy of them by default. In the extremely rare cases where the coder intends to modify the loop variable from inside the lambda, they could code around it. The other 99% of the time, we'd have had an easier life. One of those things that would be very hard to fix now, I'd imagine.Wrangle
Probably. Your reasoning is correct IMO. However, the fact that they decided to have List<T>.ForEach and Array.ForEach (both of which appeared in .NET 2.0) and they didn't provide Enumerable.ForEach extension method (in 3.5) makes me feel there are different philosophical reasons for those. Basically, I guess List<T>.ForEach is designed to be used on an already existing collection, rather than the result of your query. And BTW, I think performance is really one of the reasons they have provided ForEach for arrays and lists in the frameworkRosinski
I think this statement from that post on Eric's blog proves this: "It does not sit well with me to make the one and only sequence operator that is only useful for its side effects." [emphasis added]. Clearly, in 2.0 when Array.ForEach and List<T>.ForEach were added, they were not meant to be used as sequence operators; so the blog entry doesn't really apply to them.Rosinski
His philosophical point is about the impression given by a library facility, the direction in which it leads users. The original (no doubt good) intentions of the library designer are neither here nor there. I read Eric's point as being about the end result in terms of unnecessarily obsfucated code. Look at SO - so many times, answers have suggested query.Select(x => x).ToList().ForEach(x => ...)? So clearly the same idea is luring people to obsfucate their code with the existing ForEach methods, whether originally intended to be composable or not.Wrangle
Furthermore, suppose the current ForEach methods disappeared and the extension method he defines in the blog post was dropped into the appropriate namespaces, it would do exactly what List<T>.ForEach does, would serve as an exact replacement for it (barring some minor performance difference). So they're very much the same thing - it makes no difference what the state of mind of a library designer in Redmond was when they implemented them. :)Wrangle
R
6

When it looks neater.

Not a joke at all. Really, I mean it. Go with the more readable style in your case. For instance, if you just want to call a method on each item like:

list.ForEach(Console.WriteLine);

this style suits better. However, if you are having a hundred lines as the body of the loop, or you have nested loops and control flow constructs, the old style looks better.

Rosinski answered 23/7, 2009 at 16:40 Comment(7)
Just looks weird to me. I would find the standard foreach much easier to consume visually.Traction
AnthonyWJones: I think that's mostly because we're used to imperative programming. This style definitely looks more natural to programmers using functional languages.Rosinski
@Mehrdad - not really. Look at Haskell. When writing imperative code with side-effects (which is what this all about), they use a special monad syntax that mimics the style of imperative languages.Wrangle
@Earwicker: As a counter-example, look at F# (OCaml): Seq.iterRosinski
Oh, I thought you meant a real functional language! :PWrangle
@Mehrdad: Yes I am used to imperative programming, C# is primarily imperative in nature. Recently it has begun to support an ever increasing functional style of programming. Therein lies the problem, I want my familiar stuff that is imperative to continue to look as it always has and things that are functional to look functional. It helps me compartmentalise these very different ways of thinking.Traction
Also, this style would look very familiar to JavaScript (specifically jQuery) programmers. Although there again, this syntax suffers performance-wise compared to the language's for-loop.Abranchiate

© 2022 - 2024 — McMap. All rights reserved.