为什么在IEnumerable上没有ForEach扩展方法?

受到另一个关于缺少Zip函数的问题的启发:

为什么在IEnumerable接口上没有ForEach扩展方法?或在任何地方吗?唯一获得ForEach方法的类是List<>。有什么原因吗,也许是性能?

185886 次浏览

大多数LINQ扩展方法都返回结果。ForEach不符合这个模式,因为它什么也不返回。

ForEach方法在LINQ之前添加。如果您添加ForEach扩展,由于扩展方法的约束,它将永远不会被用于List实例。我认为没有添加的原因是为了不干扰现有的。

但是,如果您真的错过了这个漂亮的小函数,您可以推出自己的版本

public static void ForEach<T>(
this IEnumerable<T> source,
Action<T> action)
{
foreach (T element in source)
action(element);
}

我自己也一直在想,这就是为什么我总是带着这个:

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语句,它在大多数情况下都能完成这项工作。

我不希望看到以下情况:

list.ForEach( item =>
{
item.DoSomething();
} );

而不是:

foreach(Item item in list)
{
item.DoSomething();
}

后者更清晰,更容易阅读在大多数情况下,尽管输入可能会有点长。

然而,我必须承认我在这个问题上改变了立场;ForEach()扩展方法在某些情况下确实是有用的。

下面是语句和方法之间的主要区别:

  • 类型检查:foreach在运行时完成,ForEach()在编译时完成(大加!)
  • 调用委托的语法确实简单得多:objects.ForEach(DoSomething);
  • ForEach()可以被链接:尽管这样一个特性的邪恶/有用还有待讨论。

这些都是很多人提出的很好的观点,我可以理解为什么人们忽略了这个功能。我不介意微软在下一个框架迭代中添加标准ForEach方法。

如果你有f#(它将在。net的下一个版本中),你可以使用

Seq.iter doSomething myIEnumerable

是我还是List<T>Foreach几乎被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.0中出现了泛型和列表。我一直觉得Foreach是访问者模式的实现(参见Gamma, Helm, Johnson, Vlissides的设计模式)。

当然,在3.5中,我们可以使用Lambda来达到同样的效果,举个例子 http://dotnet-developments.blogs.techtarget.com/2008/09/02/iterators-lambda-and-linq-oh-my/ < / p >

因此,有很多关于ForEach扩展方法不合适的评论,因为它不像LINQ扩展方法那样返回值。虽然这是事实陈述,但并不完全正确。

LINQ扩展方法都返回一个值,所以它们可以链接在一起:

collection.Where(i => i.Name = "hello").Select(i => i.FullName);

然而,仅仅因为LINQ是使用扩展方法实现的,并不意味着扩展方法必须可以以同样的方式使用并返回一个值。编写一个扩展方法来公开不返回值的常见功能是完全有效的使用。

关于ForEach的具体参数是,基于对扩展方法的约束(即扩展方法将从来没有覆盖带有相同的签名的继承方法),可能存在一种情况,即自定义扩展方法可用于所有影响元素IEnumerable<T>的类(List<T>除外)。这可能会导致混淆,因为根据调用的是扩展方法还是继承方法,方法开始表现不同。

当你想要返回一些东西时,你可以使用select。 如果不需要,可以先使用ToList,因为您可能不想修改集合中的任何内容

虽然我同意在大多数情况下使用内置的foreach构造更好,但我发现使用ForEach<>扩展的这个变体比必须自己在常规foreach中管理索引要好一些:

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

这里的讨论给出了答案:

实际上,我所看到的具体讨论确实取决于功能的纯洁性。在一个表达式中,经常会有关于没有副作用的假设。使用ForEach是特别吸引副作用,而不仅仅是忍受它们。——基思·法默(合伙人)

基本上,这个决定是为了保持扩展方法在功能上的“纯粹”。ForEach在使用Enumerable扩展方法时会产生副作用,这不是目的。

一个解决方法是编写.ToList().ForEach(x => ...)

优点

易于理解——读者只需要知道c#附带了什么,而不需要知道任何额外的扩展方法。

语法干扰非常温和(只添加了一点无关的代码)。

通常不需要额外的内存,因为本机.ForEach()必须实现整个集合。

缺点

操作顺序并不理想。我宁愿实现一个元素,然后付诸行动,然后重复。这段代码首先实现所有元素,然后依次对它们进行操作。

如果意识到列表抛出异常,则永远无法对单个元素进行操作。

如果枚举数是无限的(就像自然数一样),那么你就不走运了。

你可以这样写扩展方法:

// 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#库有很大的不同;不熟悉您的扩展方法的读者将不知道如何理解您的代码。

@Coincoin

foreach扩展方法的真正功能包括Action<>的可重用性,而无需向代码添加不必要的方法。假设您有10个列表,并且您希望对它们执行相同的逻辑,而对应的函数不适合您的类并且不能重用。而不是10 for循环,或一个通用的函数,显然是一个不属于的助手,你可以在一个地方让你所有的逻辑(Action<>。所以,很多行被替换成

Action<blah,blah> f = { foo };


List1.ForEach(p => f(p))
List2.ForEach(p => f(p))

等等……

逻辑是在一个地方,你没有污染你的课。

还没有人指出ForEach<T>编译时类型检查的结果,其中foreach关键字是运行时检查的。

在代码中使用了这两个方法之后,我做了一些重构,我倾向于. foreach,因为我必须查找测试失败/运行时失败来找到foreach问题。

我为此写了一篇博客: http://blogs.msdn.com/kirillosenkov/archive/2009/01/31/foreach.aspx < / p > 如果你想在。net 4.0中看到这个方法,你可以在这里投票: http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=279093 < / p >

在3.5中,所有添加到IEnumerable中的扩展方法都是为了支持LINQ(注意它们是在System.Linq.Enumerable类中定义的)。在这篇文章中,我解释了为什么foreach不属于LINQ: 现有的LINQ扩展方法类似于Parallel.For? < / p >

你可以使用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,但是是惰性的和可链的。

注意,MoreLINQ NuGet提供了你正在寻找的ForEach扩展方法(以及一个执行委托并产生其结果的Pipe方法)。看到的:

我想扩展鲔的回答

如果你想调用一个方法的唯一目的是它的副作用,而不是首先迭代整个枚举对象,你可以使用这个:

private static IEnumerable<T> ForEach<T>(IEnumerable<T> xs, Action<T> f) {
foreach (var x in xs) {
f(x); yield return x;
}
}

部分原因是语言设计者从哲学角度不同意这一观点。

  • 没有(和测试……)一个功能比有一个功能更省事。
  • 它并不是真的更短(有一些传递函数的情况下,它是,但这不是主要用途)。
  • 它的目的是产生副作用,这不是linq的目的。
  • 为什么要用另一种方式来做我们已经拥有的功能呢?(为每一个关键字)

https://blogs.msdn.microsoft.com/ericlippert/2009/05/18/foreach-vs-foreach/

我的版本是一个扩展方法,允许你在T的IEnumerable上使用ForEach

public static class EnumerableExtension
{
public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
{
source.All(x =>
{
action.Invoke(x);
return true;
});
}
}