.Net - 何时使用List<T>.ForEach而不是标准的foreach循环?

3

通用列表类有一个.ForEach(Action<T> action)方法。现在我已经对它们的性能进行了一些简单的计时,似乎通用的 ForEach 方法表现较差。以下是(片段编译器友好版)的代码 -

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的执行时间为177毫秒,而GFOREACH则需要707毫秒。

现在我猜测使用它肯定有充分的理由,但我想不出来。显然性能并不是原因,那么什么情况下使用它才是最佳选择呢?

提前感谢您的帮助。


1
相关/重复?https://dev59.com/70jSa4cB1Zd3GeqPGZCe - Brandon
2个回答

7
这篇来自Eric Lippert的博客文章提供了背景:

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

他谈到了常见的建议,即扩展方法可以对 IEnumerable<T>执行相同的操作,但是哲学上的反对意见也适用于 List<T>.ForEach

这表明,也许这种方法从来就不是一个好主意,尽管它看起来“酷”。更清晰的方法是只使用 foreach

我曾建议这些方法可被视为解决闭包变量循环错误的经典方法

但在实践中,我已经更擅长发现此类错误了。


他主要谈论的是在 IEnumerable<T> 上的 .ForEach(您可以链接和组合内容),而不是在 List<T> 上,您想要在单行中对列表中的每个对象执行方法。 - Mehrdad Afshari
但是关于IEnumerable<T>上的ForEach是否可组合,没有普遍认同的意义。明显、一致的实现方式是模仿List<T>.ForEach并返回void,就像Eric在他的博客文章示例中所做的那样。因此,相同的哲学上的反对意见也适用于两者。 - Daniel Earwicker
关于循环变量闭包问题,我希望匿名委托和Lambda表达式默认情况下能够“聪明地”捕获它们的副本。在极为罕见的情况下,如果编码人员打算从Lambda内部修改循环变量,则可以绕过此限制进行编码。其余99%的时间,我们的生活将更加轻松。我想这是现在很难解决的问题之一。 - Daniel Earwicker
很可能。在我看来,你的推理是正确的。然而,他们决定提供List<T>.ForEachArray.ForEach(两者都出现在.NET 2.0中),却没有提供Enumerable.ForEach扩展方法(在3.5中),这让我感到有不同的哲学原因。基本上,我猜List<T>.ForEach被设计用于对已经存在的集合进行操作,而不是对查询结果进行操作。顺便说一下,我认为性能确实是他们在框架中为数组和列表提供ForEach的原因之一。 - Mehrdad Afshari
他的哲学观点是关于图书馆设施所给人的印象,以及它引导用户的方向。图书馆设计师最初(毫无疑问是好的)意图并不重要。我认为Eric的观点是关于最终结果,即代码不必要地变得晦涩难懂。看看Stack Overflow - 多次回答建议使用 query.Select(x => x).ToList().ForEach(x => ...)?因此,显然相同的想法诱使人们使用现有的ForEach方法来混淆他们的代码,无论最初是否旨在可组合。 - Daniel Earwicker
显示剩余2条评论

6

当它看起来更整洁时。

这并不是一个玩笑。真的,我是认真的。在您的情况下,请选择更易读的样式。例如,如果您只想对每个项调用一个方法:

list.ForEach(Console.WriteLine);

这种样式更适合。然而,如果您的循环体有一百行,或者您有嵌套的循环和控制流结构,旧样式看起来更好。


AnthonyWJones:我认为这主要是因为我们习惯了命令式编程。对于使用函数式语言的程序员来说,这种风格肯定更自然。 - Mehrdad Afshari
@Mehrdad - 不完全是这样。看看 Haskell。当编写带有副作用的命令式代码(这就是所有问题所在)时,它们使用一种特殊的单子语法,模仿命令式语言的风格。 - Daniel Earwicker
2
哦,我以为你指的是一个真正的函数式语言!:P - Daniel Earwicker
1
@Mehrdad:是的,我习惯了命令式编程,C#主要是命令式的。最近它开始支持越来越多的函数式编程风格。这就是问题所在,我希望我的熟悉的命令式东西能够继续像以前一样看起来,而函数式的东西则看起来更加函数式。这有助于我将这些非常不同的思考方式进行分隔。 - AnthonyWJones
此外,这种风格对于JavaScript(特别是jQuery)程序员来说应该非常熟悉。尽管如此,与语言的for循环相比,这种语法在性能方面存在问题。 - ehdv
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接