受到另一个询问失踪问题的启发 Zip
功能的:
为什么课堂上没有ForEach
扩展方法Enumerable
?还是在任 唯一获得ForEach
方法的类是List<>
.有没有理由错过(表演)?
在大多数情况下,该语言中已包含一个foreach语句.
我不想看到以下内容:
list.ForEach( item => { item.DoSomething(); } );
代替:
foreach(Item item in list) { item.DoSomething(); }
在大多数情况下,后者更清晰,更容易阅读,但输入可能要长一些.
但是,我必须承认我在这个问题上改变了立场; 在某些情况下,ForEach()扩展方法确实很有用.
以下是语句和方法之间的主要区别:
类型检查:foreach在运行时完成,ForEach()在编译时(Big Plus!)
调用委托的语法确实要简单得多:objects.ForEach(DoSomething);
ForEach()可以被链接:虽然这种功能的邪恶/有用性可以讨论.
这些都是很多人在这里提出的很好的观点,我可以看出为什么人们会错过这个功能.我不介意微软在下一个框架迭代中添加标准的ForEach方法.
在LINQ之前添加了ForEach方法.如果添加ForEach扩展,由于扩展方法约束,它将永远不会为List实例调用.我认为没有添加它的原因是不干扰现有的.
但是,如果你真的错过这个小功能,你可以推出自己的版本
public static void ForEach( this IEnumerable source, Action action) { foreach (T element in source) action(element); }
你可以编写这个扩展方法:
// Possibly call this "Do" IEnumerableApply (this IEnumerable source, Action action) { foreach (var e in source) { action(e); yield return e; } }
优点
允许链接:
MySequence .Apply(...) .Apply(...) .Apply(...);
缺点
在你做一些强制迭代之前,它实际上不会做任何事情.因此,不应该调用它.ForEach()
.您可以.ToList()
在最后编写,或者您也可以编写此扩展方法:
// possibly call this "Realize" IEnumerableDone (this IEnumerable source) { foreach (var e in source) { // do nothing ; } return source; }
这可能与运输C#库有太大不同; 不熟悉您的扩展方法的读者将不知道您的代码是什么.
这里的讨论给出了答案:
实际上,我目睹的具体讨论实际上取决于功能纯度.在表达式中,经常假设没有副作用.拥有ForEach是特别邀请副作用,而不仅仅是忍受它们. - Keith Farmer(合伙人)
基本上,决定保持扩展方法在功能上"纯粹".当使用Enumerable扩展方法时,ForEach会鼓励副作用,这不是意图.
虽然我同意foreach
在大多数情况下使用内置构造更好,但我发现在ForEach <>扩展中使用这种变体比在常规中foreach
自己管理索引要好一些:
public static int ForEach例(this IEnumerable list, Action action) { if (action == null) throw new ArgumentNullException("action"); var index = 0; foreach (var elem in list) action(index++, elem); return index; }
var people = new[] { "Moe", "Curly", "Larry" }; people.ForEach((i, p) => Console.WriteLine("Person #{0} is {1}", i, p));
会给你:
Person #0 is Moe Person #1 is Curly Person #2 is Larry
一个解决方法是写.ToList().ForEach(x => ...)
.
利弊
易于理解 - 读者只需知道C#附带的内容,而不是任何其他扩展方法.
句法噪音非常温和(只增加了一些极端的代码).
通常不会花费额外的内存,因为本地人.ForEach()
无论如何都必须实现整个集合.
缺点
操作顺序并不理想.我宁愿意识到一个元素,然后采取行动,然后重复.此代码首先实现所有元素,然后依次对它们进行操作.
如果实现列表抛出异常,则永远不会对单个元素执行操作.
如果枚举是无限的(就像自然数字一样),那你运气不好.
我一直想知道自己,这就是为什么我总是随身携带这个:
public static void ForEach(this IEnumerable col, Action action) { if (action == null) { throw new ArgumentNullException("action"); } foreach (var item in col) { action(item); } }
不错的小扩展方法.
因此,有很多关于ForEach扩展方法不合适的事实的评论,因为它不返回类似LINQ扩展方法的值.虽然这是一个事实陈述,但并非完全正确.
LINQ扩展方法都返回一个值,以便它们可以链接在一起:
collection.Where(i => i.Name = "hello").Select(i => i.FullName);
但是,仅仅因为使用扩展方法实现LINQ并不意味着必须以相同的方式使用扩展方法并返回值.编写扩展方法以公开不返回值的常用功能是完全有效的用法.
关于ForEach的具体论点是,基于对扩展方法的约束(即扩展方法永远不会覆盖具有相同签名的继承方法),可能存在这样一种情况:自定义扩展方法在所有可用的类上都可用IEnumerable
您可以使用(可链接但经过懒惰评估)Select
,首先执行操作,然后返回标识(如果您愿意,还可以返回其他内容)
IEnumerablepeople = new List (){"alica", "bob", "john", "pete"}; people.Select(p => { Console.WriteLine(p); return p; });
你需要确保它仍然被评估,无论Count()
是最简单的操作(枚举afaik)还是你需要的其他操作.
我很乐意看到它带入标准库:
static IEnumerableWithLazySideEffect(this IEnumerable src, Action action) { return src.Select(i => { action(i); return i; } ); }
然后上面的代码变得people.WithLazySideEffect(p => Console.WriteLine(p))
有效地等同于foreach,但是懒惰和可链接.
注意,MoreLINQ NuGet提供了ForEach
您要查找的扩展方法(以及Pipe
执行委托并产生其结果的方法)。看到:
https://www.nuget.org/packages/morelinq
https://code.google.com/p/morelinq/wiki/Operators概述