为什么IEnumerable上没有ForEach扩展方法?
受另一个问题的启发,询问有关缺less的Zip
function:
为什么Enumerable
类中没有ForEach
扩展方法? 或者在哪里? 唯一获得ForEach
方法的类是List<>
。 为什么它缺less(性能)是有原因的?
大多数情况下,这个工作语言已经包含了一个foreach语句。
我讨厌看到以下内容:
list.ForEach( item => { item.DoSomething(); } );
代替:
foreach(Item item in list) { item.DoSomething(); }
后者在大多数情况下更清晰易读,尽pipe打字时间可能稍长一些。
但是,我必须承认,我在这个问题上改变了立场。 ForEach()扩展方法在某些情况下的确会很有用。
以下是声明和方法之间的主要区别:
- types检查:foreach在运行时完成,ForEach()在编译时(Big Plus!)
- 调用委托的语法确实简单得多:objects.ForEach(DoSomething);
- ForEach()可以被链接:虽然这样一个特征的恶意/有用性是开放的讨论。
这些都是很多人在这里所做的伟大的一点,我可以看出为什么人们失去了这个function。 我不介意微软在下一个框架迭代中添加一个标准的ForEach方法。
ForEach方法在LINQ之前添加。 如果添加了ForEach扩展,由于扩展方法的限制,将永远不会调用List实例。 我认为不加的原因是不干涉现有的。
但是,如果你真的错过了这个小小的function,你可以推出自己的版本
public static void ForEach<T>( this IEnumerable<T> source, Action<T> action) { foreach (T element in source) action(element); }
你可以写这个扩展方法:
// 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; } }
优点
允许链接:
MySequence .Apply(...) .Apply(...) .Apply(...);
缺点
在你做强制迭代的事情之前,它不会做任何事情。 出于这个原因,它不应该被称为.ForEach()
。 你可以在最后写入.ToList()
,或者你也可以写这个扩展方法:
// possibly call this "Realize" IEnumerable<T> Done<T> (this IEnumerable<T> source) { foreach (var e in source) { // do nothing ; } return source; }
从运输C#库的angular度来看,这可能太重要了。 不熟悉你的扩展方法的读者不会知道你的代码是什么。
这里的讨论给出了答案:
实际上,我亲眼目睹的具体讨论实际上是颠覆了function纯度。 在一个expression式中,经常会假设没有副作用。 拥有ForEach是特别吸引副作用,而不仅仅是忍受他们。 – Keith Farmer(合伙人)
基本上决定保持扩展方法在function上是“纯粹的”。 当使用Enumerable扩展方法时,ForEach会鼓励副作用,这不是意图。
虽然我同意在大多数情况下最好使用内置的foreach
构造,但是我发现在ForEach <>扩展中使用这种变体要比在常规的foreach
pipe理索引好一点:
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; }
例
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()
将不得不实现整个集合。
缺点
操作顺序不理想。 我宁愿意识到一个因素,然后采取行动,然后重复。 这个代码首先实现所有元素,然后依次对它们进行操作。
如果实现列表抛出一个exception,你永远不会得到一个单一的元素。
如果枚举是无限的(就像自然数),那么你的运气不好。
我一直都在想我自己,这就是为什么我总是带着这个东西:
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); } }
漂亮的小扩展方法。
所以有很多关于ForEach扩展方法不合适的评论,因为它不会像LINQ扩展方法那样返回一个值。 虽然这是事实陈述,但并不完全正确。
LINQ扩展方法都会返回一个值,以便它们可以链接在一起:
collection.Where(i => i.Name = "hello").Select(i => i.FullName);
但是,仅仅因为使用扩展方法实现LINQ并不意味着必须以相同的方式使用扩展方法并返回一个值。 编写一个扩展方法来展示不返回值的通用function是非常有效的用法。
关于ForEach的具体论述是,基于对扩展方法的约束(即扩展方法不会覆盖具有相同签名的inheritance方法),可能会出现这样的情况,自定义扩展方法可用于所有类的推理IEnumerable <T
>除了List <T
>。 这可能会导致混乱,当方法开始行为有所不同,取决于是否调用扩展方法或inheritance方法。
你可以使用(可链接的,但懒惰评估) Select
,首先做你的操作,然后返回身份(或其他如果你喜欢)
IEnumerable<string> people = new List<string>(){"alica", "bob", "john", "pete"}; people.Select(p => { Console.WriteLine(p); return p});
您将需要确保它仍然被评估,无论是用Count()
(枚举afaik最便宜的操作)还是其他您需要的操作。
我很乐意看到它被引入标准库,但是:
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,但是懒惰和可链接。
@Coincoin
foreach扩展方法的实际function涉及Action<>
可重用性,而不会在代码中添加不必要的方法。 假设你有10个列表,并且你想对它们执行相同的逻辑,并且相应的函数不适合你的类,并且不被重用。 你可以把所有的逻辑放在一个地方( Action<>
,而不是有十个for循环,或者一个明显不属于帮助器的generics函数。
Action<blah,blah> f = { foo }; List1.ForEach(p => f(p)) List2.ForEach(p => f(p))
等等…
逻辑是在一个地方,你没有污染你的class级。
在3.5中,所有添加到IEnumerable的扩展方法都有LINQ支持(请注意,它们是在System.Linq.Enumerable类中定义的)。 在这篇文章中,我解释了为什么foreach不属于LINQ: 现有的类似于Parallel.For的LINQ扩展方法?
请注意, MoreLINQ NuGet提供了您正在寻找的ForEach
扩展方法(以及执行委托并产生其结果的Pipe
方法)。 看到:
大多数LINQ扩展方法返回结果。 ForEach不适合这种模式,因为它什么都不返回。
您可以使用select,当你想要返回的东西。 如果你不这样做,你可以先使用ToList,因为你可能不想修改集合中的任何东西。
我写了一篇关于它的博客文章: http : //blogs.msdn.com/kirillosenkov/archive/2009/01/31/foreach.aspx
如果你想在.NET 4.0中看到这个方法,你可以在这里投票: http : //connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=279093
是我还是List <T>。Linq几乎已经把这个过时了。 原本是有的
foreach(X x in Y)
Y只需要IEnumerable(Pre 2.0),并实现一个GetEnumerator()。 如果你看看生成的MSIL,你可以看到它是完全一样的
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.0generics和列表。 对我来说,Foreach一直觉得是Vistor模式的实现,(参见Gamma,Helm,Johnson,Vlissides的devise模式)。
现在当然在3.5中,我们可以使用一个Lambda来达到相同的效果,例如http://dotnet-developments.blogs.techtarget.com/2008/09/02/iterators-lambda-and-linq-oh-我的/
如果你有F#(这将在下一个版本的.NET中),你可以使用
Seq.iter doSomething myIEnumerable
还没有人指出ForEach <T>会导致编译时检查运行时检查foreach关键字的types。
在代码中使用了两种方法后,我进行了一些重构,我赞成.ForEach,因为我不得不寻找testing失败/运行时失败来查找foreach问题。
我想扩大阿库的答案 。
如果你想调用一个方法的唯一目的是它的副作用没有迭代整个枚举第一你可以使用这个:
private static IEnumerable<T> ForEach<T>(IEnumerable<T> xs, Action<T> f) { foreach (var x in xs) { f(x); yield return x; } }
要使用Foreach,你的列表应该被加载到主内存中。 但是由于IEnumerable的延迟加载性质,它们并没有在IEnumerable中提供ForEach。
然而,通过加载(使用ToList()),您可以将您的列表加载到内存中并利用ForEach。