foreach vs someList.ForEach(){}

显然有很多方法来迭代一个集合。 好奇的是,如果有任何分歧,或者为什么你会用另一种方式。

第一类:

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> }); 

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

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

因为.ForEach使用for循环迭代集合,所以这是有效的(编辑: .net 4.5之前 – 实现改变了,它们都抛出):

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

foreach使用枚举器,所以这是无效的:

 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; }; foreach(item in list) {foo; }; 所有他们写的代码。 例如用于从dataReader读取行的代码块。

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

list.ForEach()的缺点是:

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

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

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

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

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

我不同意list.ForEach(delegate)版本在任何重要方面是“更多function”。 它将函数传递给一个函数,但结果或程序组织没有太大的区别。

我不认为这是foreach(item in list) “说明你到底想如何做” – 一个for(int 1 = 0; i < count; i++)循环做到这一点, foreach循环留下的控制select编译器。

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

为了好玩,我popup列表到reflection器,这是由此产生的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更精细 – 更less的处理 – 更有可能将JIT转换成高效的东西。

另外,foreach()将会分配一个新的枚举器。 GC是你的朋友,但是如果你反复做同样的foreach,这将使更多的一次性物品,而不是重复使用同一个代表 – 但这是一个真正的情况。 在典型的用法中,您将看到很less或没有差异。

我知道两件不太明显的事情,使他们不同。 走吧!

首先,列表中的每个项目都有一个代表的经典错误。 如果使用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方法没有这个问题。 迭代的当前项作为parameter passing给外部lambda,然后内部lambda正确地捕获在它自己的闭包中的参数。 问题解决了。

(不幸的是,我认为ForEach是List的一个成员,而不是一个扩展方法,尽pipe你自己定义它很容易,所以你可以在任何枚举types上使用这个工具。)

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

更多在这里

我想someList.ForEach()调用可以很容易并行,而正常的foreach并不容易并行。 你可以很容易地在不同的内核上运行几个不同的代表,这对于一个普通的foreach来说并不容易。 只是我2美分

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

你可以写第二个:

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

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

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

List.ForEach()被认为是更多的function。

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

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

考虑下面的文章 list.foreach性能。

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

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

此外,有可能用for循环迭代列表。

在幕后,匿名委托变成了一个实际的方法,所以如果编译器没有select内联函数,你可能会有第二select的开销。 此外,由匿名委托实例的主体引用的任何局部variables本质上都会发生变化,因为编译器技巧会隐藏将其编译为新方法的事实。 有关C#如何实现这个魔法的更多信息:

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

有一点需要注意的是如何退出Generic .ForEach方法 – 请参阅此讨论 。 虽然这个链接似乎是说这种方式是最快的。 不知道为什么 – 你会认为一旦编译它们就相当于…

ForEach函数是generics类List的成员。

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

 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> } 

或使用labdaexpression式:

 (s) => <process the string> 

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