c# - foreach vs someList.ForEach(){}




.net generics (9)

ForEach函数是泛型类List的成员。

我创建了以下扩展来重现内部代码:

public static class MyExtension<T>
    {
        public static void MyForEach(this IEnumerable<T> collection, Action<T> action)
        {
            foreach (T item in collection)
                action.Invoke(item);
        }
    }

因此,我们正在使用一个正常的foreach(或如果你想要的循环)结束。

另一方面,使用委托函数只是定义函数的另一种方式,下面的代码:

delegate(string s) {
    <process the string>
}

相当于:

private static void myFunction(string s, <other variables...>)
{
   <process the string>
}

或使用labda表达式:

(s) => <process the string>

显然有很多方法可以迭代集合。 好奇有没有什么不同,或者你为什么会用另一种方式。

第一类:

List<string> someList = <some way to init>
foreach(string s in someList) {
   <process the string>
}

另一种方式:

List<string> someList = <some way to init>
someList.ForEach(delegate(string s) {
    <process the string>
});

我想我的头顶上,而不是我上面使用的匿名委托,你会有一个可以指定的可重用委托...


List.ForEach()被认为更实用。

List.ForEach()说明你想做什么。 foreach(item in list)也说明了你希望如何完成。 这让List.ForEach免费更改将来如何部分的实现。 例如,一个假想的未来版本的.Net可能总是并行运行List.ForEach ,假设此时每个人都有一些通常闲置的cpu核心。

另一方面, foreach (item in list)让您更多地控制循环。 例如,你知道这些项目将以某种顺序迭代,并且如果某个项目符合某些条件,则可以轻松地将其中断。


为了好玩,我将List弹出到了反射器中,这是由此产生的C#:

public void ForEach(Action<T> action)
{
    if (action == null)
    {
        ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
    }
    for (int i = 0; i < this._size; i++)
    {
        action(this._items[i]);
    }
}

同样,在foreach中使用的Enumerator中的MoveNext是这样的:

public bool MoveNext()
{
    if (this.version != this.list._version)
    {
        ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
    }
    if (this.index < this.list._size)
    {
        this.current = this.list._items[this.index];
        this.index++;
        return true;
    }
    this.index = this.list._size + 1;
    this.current = default(T);
    return false;
}

List.ForEach比MoveNext更精细 - 更少的处理 - 更有可能将JIT转换为高效..

另外,无论如何,foreach()都会分配一个新的枚举器。 GC是你的朋友,但是如果你反复做同样的foreach,这将会产生更多的一次性物品,而不是重复使用同一个代表 - 但这真是一个附带案例。 在典型的使用情况下,您将看到很少或没有差异。


你可以命名匿名代理:-)

你可以写第二个:

someList.ForEach(s => s.ToUpper())

我更喜欢它,并节省了很多打字。

正如Joachim所说,并行性更适用于第二种形式。


您展示的第二种方式使用扩展方法为列表中的每个元素执行委托方法。

这样,你有另一个委托(=方法)调用。

另外,可以用for循环遍历列表。


我们在这里有一些代码(在VS2005和C#2.0中),在那里以前的工程师不再使用list.ForEach( delegate(item) { foo;}); 而不是foreach(item in list) {foo; }; foreach(item in list) {foo; }; 为他们写的所有代码。 例如用于从dataReader读取行的代码块。

我仍然不知道他们为什么这样做。

list.ForEach()的缺点是:

  • 它在C#2.0中更加冗长。 但是,在C#3以后,可以使用“ => ”语法来制作一些很好的简洁表达式。

  • 它不太熟悉。 必须维护这些代码的人会想知道为什么你这样做。 我花了一段时间才决定没有任何理由,除了让作者看起来很聪明(其他代码的质量破坏了这一点)。 它的可读性也较差,“ }) ”在委托代码块的末尾。

  • 另请参阅Bill Wagner的书“Effective C#:50 Specific Ways to Improve Your C#”,他谈到了为什么foreach比其他循环更喜欢for或while循环 - 主要的一点是让编译器决定构建最佳方式循环。 如果未来版本的编译器设法使用更快的技术,那么您将通过使用foreach和重建来免费获得此版本,而不是更改您的代码。

  • 如果您需要退出迭代或循环,则foreach(item in list)构造允许您使用breakcontinue 。 但是你不能在foreach循环内改变列表。

我很惊讶地看到list.ForEach稍快。 但是这可能不是一个有效的使用它的理由,那将是不成熟的优化。 如果您的应用程序使用数据库或Web服务,而不是循环控制,则几乎总是会随时间推移。 你有没有将它与for循环进行比较? list.ForEach可能因为内部使用而更快,而没有包装的for循环会更快。

我不同意list.ForEach(delegate)版本以任何重要方式“更实用”。 它将函数传递给函数,但结果或程序组织没有太大差异。

我不认为foreach(item in list) “确切地说明了你想如何完成” - a for(int 1 = 0; i < count; i++)循环会这样做, foreach循环会将控制权选择为编译器。

我的感觉是,在一个新项目中,为了遵守常见用法和可读性,在大多数循环中使用foreach(item in list) ,并且只使用list.Foreach()仅用于短块,当你可以做更多的事情时优雅地或紧凑地与C#3“ => ”运营商。 在这种情况下,可能已经存在比ForEach()更具体的LINQ扩展方法。 看看Where()Select()Any()All()Max()或其他许多LINQ方法之一是否还没有做到你想要的循环。


我知道两件让人不同的事情。 走吧!

首先,存在列表中每个项目的代表的经典错误。 如果使用foreach关键字,则所有代表最终都可以引用列表的最后一项:

    // A list of actions to execute later
    List<Action> actions = new List<Action>();

    // Numbers 0 to 9
    List<int> numbers = Enumerable.Range(0, 10).ToList();

    // Store an action that prints each number (WRONG!)
    foreach (int number in numbers)
        actions.Add(() => Console.WriteLine(number));

    // Run the actions, we actually print 10 copies of "9"
    foreach (Action action in actions)
        action();

    // So try again
    actions.Clear();

    // Store an action that prints each number (RIGHT!)
    numbers.ForEach(number =>
        actions.Add(() => Console.WriteLine(number)));

    // Run the actions
    foreach (Action action in actions)
        action();

List.ForEach方法没有这个问题。 迭代的当前项目作为参数传递给外部lambda,然后内部lambda正确地捕获该参数在其自己的闭包中。 问题解决了。

(可悲的是我相信ForEach是List的成员,而不是扩展方法,尽管你自己定义它很容易,所以你可以在任何枚举类型上使用这个工具。)

其次,ForEach方法的方法有一个局限性。 如果您使用yield return实现IEnumerable,则不能在lambda内执行yield return。 因此,循环访问集合中的项目以便产生返回结果是不可能的。 您必须使用foreach关键字,并通过在循环内手动创建当前循环值的副本来解决闭包问题。

更多在这里


整个ForEach范围(委托函数)被视为一行代码(调用该函数),并且不能设置断点或步入代码。 如果发生未处理的异常,则会标记整个块。


考虑下列关于list.foreach性能的article





enumeration