灵感来自另一个关于缺失的Zip功能的问题:
为什么在Enumerable类中没有ForEach扩展方法?还是在任何地方?唯一获得ForEach方法的类是List<>。是否有原因导致它丢失(性能)?
- 这可能与前面的一篇文章所暗示的一样,因为foreach在c和vb.net(以及许多其他语言)中被支持为一个语言关键字,大多数人(包括我自己)只需根据需要和适当地自己写一个。
- 这里的相关问题链接到"官方答案"stackoverflow.com/questions/858978/&hellip;
- 另请参见stackoverflow.com/questions/200574/&hellip;
- 我认为非常重要的一点是,前臂环更容易被发现。如果您使用.foreach(…),那么当您查看代码时,很容易错过这一个。当您遇到性能问题时,这变得很重要。当然.tolist()和.toarray()有相同的问题,但它们的用法略有不同。另一个原因可能是在.foreach中修改源列表(例如,删除/添加元素)更常见,因此它不再是"纯"查询。
- 在调试并行代码时,我经常尝试用Parallel.ForEach替换Enumerable.ForEach,只是为了重新发现后者并不存在。C错过了一个让事情变得简单的技巧。
- "只需将其重写为foreach循环,语法只在开头、中间和结尾处有所不同。"为此干杯。
- 以下是实现这一点的另一种方法:VisualStudio.UserVoice.com/Forums/121579-Visual-Studio-2015/&zwnj;&8203;&hellip;
- 这里为这种缺席辩护的大多数答案和评论都是愚蠢和无知的。但既然C设计师们毫无意义,那就写下你自己的:public static void ForEach(this IEnumerable list, Action action) { foreach (T item in list) action(item); } …这里是一个类似于Select的索引:public static void ForEach(this IEnumerable list, Action action) { int i = 0; foreach (T item in list) action(item, i); }
- 这个问题仍然没有清晰合理的答案。
语言中已经包含了一个foreach语句,该语句在大多数情况下都可以执行此任务。
我不想看到以下内容:
1 2 3 4
| list.ForEach( item =>
{
item.DoSomething();
} ); |
而不是:
1 2 3 4
| foreach(Item item in list)
{
item.DoSomething();
} |
后者在大多数情况下更清晰,更容易阅读,尽管可能需要更长的时间来打字。
但是,我必须承认我在这个问题上改变了立场;foreach()扩展方法在某些情况下确实很有用。
以下是语句和方法之间的主要区别:
- 类型检查:foreach在运行时完成,foreach()在编译时完成(big plus!)
- 调用委托的语法确实要简单得多:objects.foreach(dosomething);
- foreach()可以被链接:尽管这种特性的邪恶/有用性有待讨论。
这些都是很多人在这里提出的伟大观点,我明白了为什么人们会错过这个功能。我不介意微软在下一个框架迭代中添加一个标准的foreach方法。
- 嗯,说得对。我能想到的唯一一件事就是List<>还有一个foreach方法。不幸的是,我想不出一个例子,其中有一个foreach在语法上是有用的。
- 但是如果你已经有了一个代表会发生什么呢?:)
- leppie:您在foreach中调用委托:)
- 如果剂量测量是一种局部/静态方法,那么使用foreach方法:list.foreach(剂量测量),它看起来会更干净;
- 这里的讨论给出了答案:forums.microsoft.com/msdn/&hellip;,基本上决定保持扩展方法的功能"纯粹"。前臂在使用扩展方法时会产生副作用,而这不是目的。
- 但是,mancaus,Enumerable已经有了带有副作用的扩展方法。例如计数和反转。
- 有时候扩展方法更清晰,不要试图假装使用lambda synthax:list.foreach(item=>item.dosomething())语句;
- 如果您有C背景,"foreach"可能看起来更清晰,但是内部迭代更清楚地说明了您的意图。这意味着您可以执行"sequence.asparallel().foreach(…)"之类的操作。
- @摩根,计数和反转有什么副作用?据我所知,它们对最初的枚举没有任何作用。同orderby及其亲属。原版无论它是什么都是原封不动的。
- 我不确定你关于类型检查的观点是否正确。如果可枚举的参数化,则在编译时对foreach进行类型检查。它只缺少对非泛型集合的编译时类型检查,就像假设的foreach扩展方法一样。
- 哈哈,我做了,但我决定,因为这是从反对每一个咆哮开始的,然后它仍然处于那个位置。
- 扩展方法在使用点标记的链表LINQ中非常有用,例如:col.Where(...).OrderBy(...).ForEach(...)。更简单的语法,foreach()中没有冗余局部变量或长括号的屏幕污染。
- 老问题,但你最后一个要点是错误的。foreach不能被链接,因为它的返回类型是void。当然,自定义实现可以是。
- 当您只想对结果执行一次操作时,链末尾的.foreach()方法看起来更干净。此外,foreach可以为索引提供过载,因此我们不必每次都重复通常的工作区。
- 使用现有的语言特性作为论据有点弱,不是吗?那我们就永远不会超越C 1。
- 这并不能解释为什么在IEnumerable<>上没有foreach方法。
- 给你。投票吧!VisualStudio.uservoice.com/forums/121579-Visual-Studio-2015/&zwnj;&8203;&hellip;
- 我把它命名为Apply,让它返回IEnumerable,这样它就可以出现在链的任何位置。public static IEnumerable Apply(this IEnumerable list, Action action) { foreach (T item in list) { action(item); } return list; }。
- @Olivierjacot指出你的应用是误导性的,毫无意义的低效,如果可枚举的有副作用,比如从流中读取,那么它就不起作用。用collection.Foreach(x => { f(x); g(x); })代替collection.Apply(x => f(x)).Apply(x => g(x))。
- "我不想看到下面的内容"——问题不是关于你的个人偏好,而是你通过添加额外的换行符和一对额外的大括号使代码更加令人讨厌。以东十一〔十二〕并不是那么可恨。谁说是清单?显而易见的用例位于链的末尾;使用foreach语句,您必须将整个链放在in之后,并在块内执行操作,这远不是自然的或一致的。
- 我认为这样做会很好:用items.Where(item => [test on item]).ForEach([do stuff])代替items.Where(item => [test on item]).ToList().ForEach([do stuff])。"tolist"似乎没有添加任何内容,并使其可读性稍差(在我看来,当您有几个链接时,嵌套for循环绝对不可读)。
- 我看到的最坏的答案。你的偏好不是原因
在LINQ之前添加了foreach方法。如果添加foreach扩展,则不会因为扩展方法约束而为列表实例调用它。我认为它没有被添加的原因是不干扰现有的一个。
但是,如果你真的错过了这个好功能,你可以推出自己的版本
1 2 3 4 5 6 7
| public static void ForEach<T>(
this IEnumerable<T> source,
Action<T> action)
{
foreach (T element in source)
action(element);
} |
- 扩展方法允许这样做。如果已经存在给定方法名的实例实现,则使用该方法而不是扩展方法。因此,可以实现foreach的扩展方法,而不让它与list<>.foreach方法冲突。
- Cameron,相信我,我知道扩展方法是如何工作的:)List继承了IEnumerable,所以这将是不明显的事情。
- 从扩展方法编程指南(msdn.microsoft.com/en-us/library/bb383977.aspx):永远不会调用与接口或类方法具有相同名称和签名的扩展方法。我觉得很明显。
- 我说的是不同的话吗?我认为很多类都有foreach方法是不好的,但是它们中的一些可能工作方式不同。
- 啊,对不起。当您说"干扰现有版本"时,我认为您是在建议扩展方法将覆盖本地版本。重读使我意识到我们谈论的是同一件事。:)
- 关于list<>.foreach和array.foreach,需要注意的一点是,它们实际上没有在内部使用foreach构造。它们都使用普通的for循环。也许这是一个性能方面的问题(diditwith.net/2006/10/05/PerformanceOfferachvslistforEach.&zwnj;&8203;aspx)。但考虑到这一点,array和list都实现了foreach方法,令人惊讶的是它们至少没有为ilist<>实现扩展方法,如果不是为ienumerable<>实现扩展方法的话。
- "永远不会因为扩展方法约束而对列表实例调用它——只要它们具有相同的语义,就不相关。"我认为,当许多类都有foreach方法时,这是不好的,但是其中的一些方法可能工作得不同——但在这种情况下,它们的行为并不不同。我认为"——不是一种有效的回答形式。对猜测的"为什么"问题的回答应输入注释,而不是答案。事实上,你的猜测是错误的。不过,提供一个实现是值得称赞的。
- @Mattdotson编译器在特殊情况下使用foreach语句对数组进行索引循环,因此它的速度至少与ForEach()一样快。最终,性能将取决于JIT生成的代码。
您可以编写这个扩展方法:
1 2 3 4 5 6 7 8 9
| // Possibly call this"Do"
IEnumerable<T> Apply<T> (this IEnumerable<T> source, Action<T> action)
{
foreach (var e in source)
{
action(e);
yield return e;
}
} |
赞成的意见
允许链接:
1 2 3 4
| MySequence
.Apply(...)
.Apply(...)
.Apply(...); |
欺骗
它实际上不会做任何事情,除非您做一些强制迭代的事情。因此,不应称之为.ForEach()。您可以在结尾处编写.ToList(),也可以编写这个扩展方法:
1 2 3 4 5 6 7 8 9 10 11
| // possibly call this"Realize"
IEnumerable<T> Done<T> (this IEnumerable<T> source)
{
foreach (var e in source)
{
// do nothing
;
}
return source;
} |
这可能太重要了,因为与运输C库不同;不熟悉您的扩展方法的读者不知道如何使用您的代码。
- 如果写得像:{foreach (var e in source) {action(e);} return source;},那么第一个函数可以与"实现"方法一起使用。
- 你不能用select代替apply方法吗?在我看来,对每个元素应用一个操作并生成它正是select所做的。如numbers.Select(n => n*2);。
- 我认为Apply比使用Select更易读。当阅读一个select语句时,我把它理解为"从内部获取一些东西",其中apply是"做一些工作"。
- @金谷的问题是有副作用的可枚举(读取文件)无法正常工作。
- @Rasmusvhansen实际上,Select()表示对每个元素应用一个函数,并返回函数的值,其中Apply()表示对每个元素应用Action并返回原始元素。
- 这相当于Select(e => { action(e); return e; })。
这里的讨论给出了答案:
Actually, the specific discussion I witnessed did in fact hinge over functional purity. In an expression, there are frequently assumptions made about not having side-effects. Having ForEach is specifically inviting side-effects rather than just putting up with them. -- Keith Farmer (Partner)
基本上决定保持扩展方法的功能"纯粹"。当使用可枚举扩展方法时,foreach会鼓励副作用,而这不是目的。
- 另请参见blogs.msdn.com/b/ericlippert/archive/2009/05/18/&hellip;。这样做违反了所有其他序列运算符所基于的函数编程原则。显然,调用此方法的唯一目的是产生副作用
- 那种推理完全是假的。用例是需要副作用…如果没有foreach,则必须编写foreach循环。前臂的存在不鼓励副作用,其缺失也不会减少副作用。
虽然我同意在大多数情况下最好使用内置的foreach构造,但我发现在foreach<>扩展中使用这种变体比在常规foreach中自己管理索引要好一些:
1 2 3 4 5 6 7 8 9 10 11
| public static int ForEach<T >(this IEnumerable <T > list, Action <int, T > action )
{
if (action == null) throw new ArgumentNullException ("action");
var index = 0;
foreach (var elem in list )
action (index ++, elem );
return index ;
} |
例子
1 2
| var people = new[] {"Moe", "Curly", "Larry" };
people .ForEach((i, p ) => Console .WriteLine("Person #{0} is {1}", i, p )); |
会给你:
1 2 3
| Person #0 is Moe
Person #1 is Curly
Person #2 is Larry |
- 这是一个很好的扩展方法。我以后会用的。
- 很好的扩展,但是您可以添加一些文档吗?返回值的预期用途是什么?为什么索引var不是专门的int?样品使用情况?
- mark:返回值是列表中的元素数。由于隐式键入,索引被专门键入为int。
- @Chris,根据上面的代码,返回值是列表中最后一个值的从零开始的索引,而不是列表中元素的数量。
- @埃里克,谢谢你接电话。
- @克里斯,不,不是这样的:索引是在获取值之后递增的(后缀递增),所以在处理第一个元素之后,索引将是1,依此类推。所以返回值实际上是元素计数。
- @Martinstettner,不,什么?此方法应该返回元素计数。
- @Martinstettner 5月16日埃里克·史密斯的评论来自于早期版本。他在5/18更正了代码,以返回正确的值。
一个解决方法是编写.ToList().ForEach(x => ...)。
赞成的意见
易于理解-读者只需要知道C附带的是什么,而不需要任何附加的扩展方法。
句法噪声很小(只增加了一点多余的代码)。
通常不需要额外的内存,因为本机.ForEach()无论如何必须实现整个集合。
欺骗
操作顺序不理想。我宁愿认识到一个元素,然后付诸行动,然后重复。此代码首先实现所有元素,然后依次对它们进行操作。
如果认识到这个列表抛出了一个异常,就永远不能对单个元素执行操作。
如果枚举是无限的(像自然数一样),你就走运了。
- a)这根本不是问题的答案。b)如果前面提到,虽然没有Enumerable.ForEach方法,但有一个List.ForEach方法,这种反应会更清楚。c)"通常不需要额外的内存,因为无论如何,本机.foreach()必须实现整个集合。"——完全而彻底的胡说八道。下面是Enumerable的实现。如果它提供了,c将提供的foreach是:public static void ForEach(this IEnumerable list, Action action) { foreach (T item in list) action(item); }。
我一直在想,我自己,这就是为什么我总是随身携带这个:
1 2 3 4 5 6 7 8 9 10 11
| public static void ForEach<T >(this IEnumerable <T > col, Action <T > action )
{
if (action == null)
{
throw new ArgumentNullException ("action");
}
foreach (var item in col )
{
action (item );
}
} |
很好的小扩展方法。
- 列可以为空…你也可以检查一下。
- 是的,我确实在我的图书馆登记,我只是在那里做的时候错过了它。
- 我发现有趣的是,每个人都会检查动作参数是否为空,但从不检查source/col参数。
- 我发现有趣的是,当不检查异常中的结果时,每个人都会检查参数是否为空…
- 一点也不。空引用异常不会告诉您出错的参数的名称。您也可以检查循环,这样您就可以抛出一个特定的空引用,说明col[index]是空的,原因相同。
所以有很多关于foreach扩展方法不合适的事实的评论,因为它不返回像linq扩展方法那样的值。虽然这是事实陈述,但并非完全正确。
LINQ扩展方法都会返回一个值,以便将它们链接在一起:
1
| collection.Where(i => i.Name ="hello").Select(i => i.FullName); |
但是,仅仅因为LINQ是使用扩展方法实现的,并不意味着扩展方法必须以相同的方式使用并返回值。编写扩展方法以公开不返回值的公共功能是一种完全有效的用法。
关于foreach的具体论点是,基于对扩展方法的约束(即扩展方法永远不会重写具有相同签名的继承方法),可能会有这样一种情况,即自定义扩展方法可用于实现IEnumerabl以外的所有类。当方法的行为开始不同时,这可能会导致混淆,这取决于是否调用扩展方法或继承方法。
您可以使用(可链接的,但评估不及时的)Select,首先执行操作,然后返回身份(如果您愿意,还可以返回其他内容)。
1 2
| IEnumerable <string> people = new List <string>(){"alica", "bob", "john", "pete"};
people .Select(p => { Console .WriteLine(p ); return p ; }); |
您需要确保它仍然被评估,要么使用Count()(枚举afaik的最便宜操作),要么使用您无论如何需要的另一个操作。
不过,我很想看到它被带到标准图书馆:
1 2 3
| static IEnumerable<T> WithLazySideEffect(this IEnumerable<T> src, Action<T> action) {
return src.Select(i => { action(i); return i; } );
} |
然后,上面的代码就变成了people.WithLazySideEffect(p => Console.WriteLine(p)),它实际上相当于foreach,但是很懒惰而且可以链接。
- 太棒了,它从来没有点击返回的选择会像这样工作。方法语法的一个很好的用法,因为我认为您不能用表达式语法来实现这一点(因此我以前没有这样想过)。
- @Alexkeysmith如果C有一个序列操作符,比如它的前驱,你可以用表达式语法来完成它。但是foreach的重点是它对void类型执行操作,并且不能在c_的表达式中使用void类型。
@ Coincoin
foreach扩展方法的真正功能涉及到Action<>的可重用性,而无需向代码中添加不必要的方法。假设您有10个列表,并且您希望对它们执行相同的逻辑,并且相应的函数不适合您的类,并且不被重用。您可以将所有逻辑保存在一个位置(Action<>中),而不必使用十个for循环或一个显然不属于助手的通用函数。因此,许多行被替换为
1 2 3 4
| Action<blah,blah> f = { foo };
List1.ForEach(p => f(p))
List2.ForEach(p => f(p)) |
等。。。
逻辑是在一个地方,你没有污染你的班级。
- foreach(var p in list1.concat(list2).concat(list3))…做些事情…}
- 如果它和f(p)一样简单,那么省去{ }作为简写:for (var p in List1.Concat(List2).Concat(List2)) f(p);。
请注意,morelinq nuget提供了您要查找的ForEach扩展方法(以及执行委托并生成其结果的Pipe方法)。见:
- https://www.nuget.org/packages/morelinq
- https://code.google.com/p/morelinq/wiki/operatorsoverview
我想扩大对阿库的回答。
如果只为了一个方法的副作用而不首先迭代整个可枚举的方法,那么可以使用以下方法:
1 2 3 4 5
| private static IEnumerable<T> ForEach<T>(IEnumerable<T> xs, Action<T> f) {
foreach (var x in xs) {
f(x); yield return x;
}
} |
- 与Select(x => { f(x); return x;})相同
如果您有f(将在.NET的下一个版本中出现),则可以使用
Seq.iter doSomething myIEnumerable
- 因此,微软选择不为C和VB中的IEnumerable提供foreach方法,因为它不是纯粹的函数性的;但他们确实用功能性更强的语言f提供了它(iter)。他们的逻辑暗指我。
它是我还是列表.forEach几乎被linq废弃了。原来有
其中y必须是IEnumerable(2.0之前的版本),并实现getEnumerator()。如果查看生成的msil,可以看到它与
1 2 3 4 5 6 7
| IEnumerator<int> enumerator = list.GetEnumerator();
while (enumerator.MoveNext())
{
int i = enumerator.Current;
Console.WriteLine(i);
} |
(MSIL见http://alski.net/post/0a-for-foreach-forfirst-forlast0a-0a-.aspx)
然后在dotnet2.0中,出现了泛型和列表.forEach一直让我觉得它是vistor模式的一个实现(参见gamma、helm、johnson、vlistides的设计模式)。
当然,在3.5中,我们可以使用lambda来达到同样的效果,例如,尝试一下http://dotnet-developments.blogs.techtarget.com/2008/09/02/iterators-lambda-and-linq-oh-my/
- 我相信为"foreach"生成的代码应该有一个tryfinally块。因为t的IEnumerator继承自接口IDisposable。因此,在生成的finally块中,应该释放T实例的IEnumerator。
大多数LINQ扩展方法都返回结果.forEach不适合此模式,因为它什么也不返回。
- foreach使用action,这是委托的另一种形式
- 除了leppie的权利,所有可枚举扩展方法都返回一个值,因此foreach不适合该模式。代表不参与此项工作。
- 只是一个扩展方法不必返回结果,它的目的是添加一种方法来调用频繁使用的操作
- 的确,Slace。如果它不返回值呢?
- 在函数语言中,"void"通常不存在,因此返回空的"unit".forEach方法可以允许连续性或某些这样的…至少,我是这么看的。
- 它可以返回标识,这可以解决这个问题。
- @然后您会遇到多个枚举问题。
- @麦凯:怎么了?无论如何,您将返回一个新的枚举,该枚举可以再次遍历,即使原始枚举只能遍历一次。
- @返回标识的martijn意味着枚举在方法的foreach中同时运行,然后也返回,然后可以是.ToList()ed。foreach和.ToList()都通过集合枚举。没有其他Linq方法,通过链接或其他方式,通过枚举进行乘法枚举。在扩展方法中,foreach中的yield return将更具性能。但在枚举之前不会实际预测。
- @Martijn,即public IEnumerable Foreach(this IEnumerable items, Action action) { /* error check */ foreach (var item in items) { action(item); yield return item; } }。
- @用户29439在函数语言中,不需要foreach,因为您可以将单元函数传递给map(微软愚蠢地称之为Select)。
- "除了leppie的权利,所有可枚举扩展方法都返回一个值,因此foreach不适合该模式。代表们不会来这里的。"——哇!当然他们会……c特殊情况void;它不能是类型参数的值。这就是为什么除了func之外还需要采取行动的原因。如果void可以是一个类型参数,那么void函数可以传递给Select(在非脑损伤语言中称为map,不需要foreach。
- @麦凯说,"但在列举之前,它不会真正预测",这使得它毫无意义。你需要把它作为foreach声明的集合,或者在ReallyForEach没有yield的地方做...ForEach(x => f(x)).ReallyForEach(x => {})。事实上foreach()应该结束一个链,就像Count()、Min()和Max()结束链……无返回IEnumerable。
- @McKay P.S.你已经可以用Select(x => { f(x); return x; })做你的foreach版本了。
- @Jimbalter正是我的观点(从3年前开始)。拥有这样的前臂是没有意义的。
- @我写的不是"你的观点"。同样:"事实是foreach()应该像count()、min()和max()一样结束一个链……没有一个返回IEnumerable。"一个完全合理的实现是public static void ForEach(this IEnumerable list, Action action) { foreach (T item in list) action(item); }--它返回void,正如它必须返回的那样,而不是IEnumerable,这确实很糟糕。
- @Jimbalter也不需要您的新实现。当然,你可以说它"遵循规则",但除了新的功能开销之外,它不会给你任何东西。
- @麦凯,什么都不需要……我们可以用机器代码来写,或者干脆放弃写程序。但这里的问题是foreach函数,如果没有实现,它就不存在。正如许多其他评论所指出的,这其中有价值。我从来没有说过需要什么;那是一个临时工,我要求我的通讯员有一定程度的诚意参与,所以我不会进一步参与。
- @Jimbalter对不起,我不是说"需要"。我只是不知道它如何增加真正的价值博客。msdn.microsoft.com/ericlippet/2009/05/18/&hellip;
- stackoverflow.com/a/52800192/8384
部分原因是语言设计者从哲学的角度不同意。
- 没有(和测试…)一个特性比有一个特性的工作要少。
- 它并不是很短(在某些传递函数的情况下,它是这样的,但这不是主要用途)。
- 它的目的是产生副作用,这不是Linq的目的。
- 为什么要用另一种方法来完成我们已经拥有的功能?(foreach关键字)
“foreach” vs “ForEach”
我写了一篇关于它的博客:http://blogs.msdn.com/kirillosenkov/archive/2009/01/31/foreach.aspx
如果希望在.NET 4.0中看到此方法,可以在此处投票:http://connect.microsoft.com/VisualStudio/Feedback/ViewFeedback.aspx?反馈ID=279093
在3.5中,所有添加到IEnumerable的扩展方法都是为了支持LINQ(请注意,它们是在System.Linq.Enumerable类中定义的)。在这篇文章中,我解释了为什么foreach不属于linq:现有的类似于parallel.for的LINQ扩展方法?
当您想返回某些内容时,可以使用Select。如果不想,可以先使用tolist,因为您可能不想修改集合中的任何内容。
- a)这不是问题的答案。b)tolist需要额外的时间和记忆。
要使用foreach,应该将列表加载到主内存中。但是由于IEnumerable的延迟加载特性,它们没有在IEnumerable中提供foreach。
但是,通过预先加载(使用to list()),您可以将列表加载到内存中并利用foreach。
- 这种懒惰可以在运行时观察到。例如,如果var subList = list.Where(lambda),那么进入foreach (var e in subList)将跳转到Where'slambda表达式。注意,subList是System.Linq.Enumerable.WhereListIterator。
还没有人指出foreach会导致编译时类型检查,其中foreach关键字是运行时检查的。
在代码中使用了这两种方法的重构之后,我更喜欢.foreach,因为我必须查找测试失败/运行时失败以查找foreach问题。
- 是真的吗?我看不出foreach的编译时检查有多少。当然,如果您的集合是非泛型IEnumerable,则循环变量将是一个object,但对于假设的foreach扩展方法也是如此。
- 这在公认的答案中提到。然而,这完全是错误的(上面的理查德·普尔是正确的)。