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使用一个
1 | someList.ForEach(x => { if(x.RemoveMe) someList.Remove(x); }); |
鉴于
1 2 | foreach(var item in someList) if(item.RemoveMe) someList.Remove(item); |
tl;dr:不要将此代码复制粘贴到应用程序中!
这些例子并不是最佳实践,它们只是为了说明
从
通常,如果要从列表中删除多个项目,则需要将确定要从实际删除中删除哪些项目分开。它不会保持代码紧凑,但它保证不会遗漏任何项目。
我们这里有一些代码(在VS2005和C 2.0中),在这些代码中,以前的工程师们竭尽全力地使用
我仍然不知道他们为什么这样做。
在C 2.0中更详细。但是,在C 3以后的版本中,您可以使用"
=> 语法来生成一些非常简洁的表达式。不太熟悉。必须维护此代码的人会想知道您为什么这样做。我花了一段时间才决定,除了让作者看起来很聪明之外,没有任何理由(其他代码的质量破坏了这一点)。它的可读性也较差,在委托代码块的末尾有"
}) "。另请参见比尔·瓦格纳的书《有效的C:50种改进C的具体方法》,其中他谈到了为什么foreach比for或while循环等其他循环更受欢迎——主要的一点是让编译器决定构造循环的最佳方法。如果未来版本的编译器成功地使用了更快的技术,那么您可以通过使用foreach和重新构建(而不是更改代码)免费获得这一点。
如果需要退出迭代或循环,
foreach(item in list) 构造允许您使用break 或continue 。但不能在foreach循环内更改列表。
我很惊讶地看到
我不同意
我不认为
我的感觉是,在一个新的项目中,为了遵守常见用法和可读性,大多数循环使用
为了好玩,我将列表弹出到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关键字,并通过在循环中手动复制当前循环值来解决关闭问题。
这里更多
我想
您可以将匿名委托命名为:-)
你可以把第二个写为:
1 | someList.ForEach(s => s.ToUpper()) |
我更喜欢,而且省去了很多打字。
正如约阿希姆所说,并行性更容易应用于第二种形式。
整个foreach范围(委托函数)被视为一行代码(调用函数),您不能设置断点或单步执行代码。如果发生未处理的异常,则标记整个块。
foreach()被认为更具功能性。
另一方面,
在幕后,匿名委托被转换成实际的方法,这样如果编译器没有选择内联函数,那么第二个选择可能会带来一些开销。此外,匿名委托示例体引用的任何局部变量的性质都会发生变化,这是因为编译器的技巧隐藏了将其编译为新方法的事实。更多关于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循环迭代列表。