关于C#:foreach与someList.foreach()

foreach vs someList.ForEach(){}

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

第一种类型:

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

其他方式:

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

我想在我的头脑中,不是我上面使用的匿名委托,而是您可以指定一个可重用的委托…


两者之间有一个重要而有用的区别。

因为.foreach使用一个for循环来迭代集合,所以这是有效的(编辑:在.NET 4.5之前-实现已更改,并且它们都抛出):

1
someList.ForEach(x => { if(x.RemoveMe) someList.Remove(x); });

鉴于foreach使用枚举器,因此这是无效的:

1
2
foreach(var item in someList)
  if(item.RemoveMe) someList.Remove(item);

tl;dr:不要将此代码复制粘贴到应用程序中!

这些例子并不是最佳实践,它们只是为了说明ForEach()foreach之间的区别。

for循环中的列表中删除项目可能会产生副作用。最常见的是在这个问题的评论中描述的。

通常,如果要从列表中删除多个项目,则需要将确定要从实际删除中删除哪些项目分开。它不会保持代码紧凑,但它保证不会遗漏任何项目。


我们这里有一些代码(在VS2005和C 2.0中),在这些代码中,以前的工程师们竭尽全力地使用list.ForEach( delegate(item) { foo;});,而不是foreach(item in list) {foo; };来处理他们编写的所有代码。例如,用于从数据读取器读取行的代码块。

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

list.ForEach()的缺点是:

  • 在C 2.0中更详细。但是,在C 3以后的版本中,您可以使用"=>语法来生成一些非常简洁的表达式。

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

  • 另请参见比尔·瓦格纳的书《有效的C:50种改进C的具体方法》,其中他谈到了为什么foreach比for或while循环等其他循环更受欢迎——主要的一点是让编译器决定构造循环的最佳方法。如果未来版本的编译器成功地使用了更快的技术,那么您可以通过使用foreach和重新构建(而不是更改代码)免费获得这一点。

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

我很惊讶地看到list.ForEach的速度稍微快一些。但这可能不是一个完整使用它的合理理由,那将是过早的优化。如果您的应用程序使用的是一个数据库或Web服务,而不是循环控制,那么它几乎永远都是时间的去向。你是否也将它与一个for循环作了基准测试?由于在内部使用list.ForEach,因此list.ForEach可能更快,而不使用包装器的for循环将更快。

我不同意list.ForEach(delegate)版本在任何意义上都是"更实用"的。它确实将一个函数传递给了一个函数,但是在结果或程序组织上没有很大的区别。

我不认为foreach(item in list)确切地说明了你想要它做什么——一个for(int 1 = 0; i < count; i++)循环这样做,一个foreach循环将控制权的选择权留给编译器。

我的感觉是,在一个新的项目中,为了遵守常见用法和可读性,大多数循环使用foreach(item in list),并且仅在短块中使用list.ForEach(),当您可以用c 3"=>"运算符更优雅或紧凑地执行某些操作时。在这种情况下,可能已经有了一个比ForEach()更为具体的LINQ扩展方法。看看Where()Select()Any()All()Max()或其他许多linq方法中的一种是否已经没有实现您想要的循环。


为了好玩,我将列表弹出到Reflector中,结果是C:

1
2
3
4
5
6
7
8
9
10
11
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使用的枚举器中的moveNext是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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;
}

与moveNext相比,list.foreach的剪裁要小得多——处理量要小得多——它更有可能实现高效的JIT。

此外,foreach()将分配一个新的枚举器。GC是您的朋友,但是如果您重复执行相同的foreach,这将生成更多的一次性对象,而不是重用相同的委托——但是——这实际上是一个边缘情况。在典型的用法中,您将看到很少或没有区别。


我知道两个让他们与众不同的模糊事物。走吧!

首先,有一个典型的错误,即为列表中的每一项指定一个委托。如果使用foreach关键字,则所有代理最终都可以引用列表的最后一项:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    // 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是列表的成员,而不是扩展方法,尽管您自己很容易定义它,所以您可以在任何可枚举类型上使用此功能。)

其次,foreach方法有局限性。如果使用yield-return实现IEnumerable,则不能在lambda内部执行yield-return。因此,使用此方法不可能通过集合中的项循环来生成返回的内容。您必须使用foreach关键字,并通过在循环中手动复制当前循环值来解决关闭问题。

这里更多


我想someList.ForEach()调用可以很容易地并行化,而正常的foreach调用则不容易并行化。您可以在不同的核心上轻松地运行几个不同的委托,这对于普通的foreach来说并不容易。只有我2美分


您可以将匿名委托命名为:-)

你可以把第二个写为:

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

我更喜欢,而且省去了很多打字。

正如约阿希姆所说,并行性更容易应用于第二种形式。


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


foreach()被认为更具功能性。

List.ForEach()说你想做什么。foreach(item in list)也准确地说明了你希望它如何完成。这使得List.ForEach可以自由地改变未来如何部分的实现。例如,假设未来的.NET版本可能总是并行运行List.ForEach,前提是此时每个人都有一些CPU核心,这些核心通常处于空闲状态。

另一方面,foreach (item in list)给了你一点对循环的控制。例如,您知道这些项将以某种顺序进行迭代,并且如果一个项满足某些条件,您可以很容易地在中间中断。


在幕后,匿名委托被转换成实际的方法,这样如果编译器没有选择内联函数,那么第二个选择可能会带来一些开销。此外,匿名委托示例体引用的任何局部变量的性质都会发生变化,这是因为编译器的技巧隐藏了将其编译为新方法的事实。更多关于C如何施法的信息:

http://blogs.msdn.com/oldnewthing/archive/2006/08/04/688527.aspx


foreach函数是泛型类列表的成员。

我创建了以下扩展来复制内部代码:

1
2
3
4
5
6
7
8
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(或者一个循环,如果需要的话)。

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

1
2
3
delegate(string s) {
    <process the string>
}

相当于:

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

或者使用labda表达式:

1
(s) => <process the string>

要小心的一件事是如何退出generic.foreach方法-请参见本讨论。虽然这个链接似乎说这种方式是最快的。不知道为什么-你会认为它们在编译后是等价的…


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

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

此外,还可以使用for循环迭代列表。