关于C#:为什么IEnumerable上没有foreach扩展方法?

Why there is no ForEach extension method on IEnumerable?

灵感来自另一个关于缺失的Zip功能的问题:

为什么在Enumerable类中没有ForEach扩展方法?还是在任何地方?唯一获得ForEach方法的类是List<>。是否有原因导致它丢失(性能)?


语言中已经包含了一个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方法。


在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);
}


您可以编写这个扩展方法:

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库不同;不熟悉您的扩展方法的读者不知道如何使用您的代码。


这里的讨论给出了答案:

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会鼓励副作用,而这不是目的。


虽然我同意在大多数情况下最好使用内置的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


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

赞成的意见

易于理解-读者只需要知道C附带的是什么,而不需要任何附加的扩展方法。

句法噪声很小(只增加了一点多余的代码)。

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

欺骗

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

如果认识到这个列表抛出了一个异常,就永远不能对单个元素执行操作。

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


我一直在想,我自己,这就是为什么我总是随身携带这个:

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);
    }
}

很好的小扩展方法。


所以有很多关于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,但是很懒惰而且可以链接。


@ 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))

等。。。

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


请注意,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;
    }
}


如果您有f(将在.NET的下一个版本中出现),则可以使用

Seq.iter doSomething myIEnumerable


它是我还是列表.forEach几乎被linq废弃了。原来有

1
foreach(X x in Y)

其中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/


大多数LINQ扩展方法都返回结果.forEach不适合此模式,因为它什么也不返回。


部分原因是语言设计者从哲学的角度不同意。

  • 没有(和测试…)一个特性比有一个特性的工作要少。
  • 它并不是很短(在某些传递函数的情况下,它是这样的,但这不是主要用途)。
  • 它的目的是产生副作用,这不是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,因为您可能不想修改集合中的任何内容。


要使用foreach,应该将列表加载到主内存中。但是由于IEnumerable的延迟加载特性,它们没有在IEnumerable中提供foreach。

但是,通过预先加载(使用to list()),您可以将列表加载到内存中并利用foreach。


还没有人指出foreach会导致编译时类型检查,其中foreach关键字是运行时检查的。

在代码中使用了这两种方法的重构之后,我更喜欢.foreach,因为我必须查找测试失败/运行时失败以查找foreach问题。