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

Why is there no ForEach extension method on IEnumerable?

提问人:Cameron MacFarland 提问时间:9/19/2008 最后编辑:SergeyCameron MacFarland 更新时间:8/5/2022 访问量:198992

问:

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

为什么接口上没有扩展方法?还是在任何地方?唯一获得方法的类是 。它缺失是有原因的,也许是性能?ForEachIEnumerableForEachList<>

C# .NET vb.net 扩展方法

评论

2赞 Benjol 6/4/2009
相关问题在这里,链接到“官方答案”stackoverflow.com/questions/858978/...
1赞 goodeye 6/27/2012
另请参阅 stackoverflow.com/questions/200574/...
1赞 Daniel Bişar 10/31/2014
我认为非常重要的一点是,foreach 循环更容易被发现。如果您使用 .ForEach(...) 当您查看代码时,很容易错过这一点。当您遇到性能问题时,这一点变得很重要。答案是肯定的。ToList() 和 .ToArray() 有同样的问题,但它们的使用方式略有不同。另一个原因可能是它在 .ForEach 修改源列表(例如删除/添加元素),使其不再是“纯”查询。
0赞 Colonel Panic 6/1/2016
在调试并行化代码时,我经常尝试交换,但重新发现后者不存在。C# 错过了让事情变得简单的技巧。Parallel.ForEachEnumerable.ForEach
0赞 chrisb 9/19/2008
它可能正如之前的一篇文章所暗示的那样,因为 foreach 在 C# 和 VB.NET(以及许多其他人)中支持作为语言关键字,大多数人(包括我自己)只是根据需要和适当地自己编写一个。

答:

3赞 leppie 9/19/2008 #1

大多数 LINQ 扩展方法都返回结果。ForEach 不适合此模式,因为它不返回任何内容。

评论

2赞 Aaron Powell 9/19/2008
ForEach 使用 Action<T>,这是另一种形式的委托
2赞 Cameron MacFarland 9/19/2008
除了 leppie 的 right 之外,所有 Enumerable 扩展方法都返回一个值,因此 ForEach 不适合该模式。委托不涉及此。
0赞 Aaron Powell 9/19/2008
只是扩展方法不必返回结果,它的目的是添加一种调用常用操作的方法
1赞 TraumaPony 9/20/2008
确实,Slace。那么,如果它没有返回值怎么办?
1赞 McKay 8/6/2013
@Martijn即public IEnumerable<T> Foreach<T>(this IEnumerable<T> items, Action<T> action) { /* error check */ foreach (var item in items) { action(item); yield return item; } }
89赞 aku 9/19/2008 #2

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

但是,如果您真的错过了这个不错的小功能,则可以推出自己的版本

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

评论

15赞 Cameron MacFarland 9/19/2008
扩展方法允许这样做。如果给定方法名称已存在实例实现,则使用该方法而不是扩展方法。因此,您可以实现扩展方法 ForEach,而不会让它与 List<> 发生冲突。ForEach 方法。
3赞 aku 9/19/2008
Cameron,相信我,我知道扩展方法是如何工作的:)List 继承了 IEnumerable,所以这将是不明显的事情。
12赞 Cameron MacFarland 9/19/2008
摘自扩展方法编程指南 (msdn.microsoft.com/en-us/library/bb383977.aspx):永远不会调用与接口或类方法具有相同名称和签名的扩展方法。对我来说似乎很明显。
3赞 aku 9/19/2008
我说的不一样吗?我认为当许多类都有 ForEach 方法时,这并不好,但其中一些可能以不同的方式工作。
5赞 Matt Dotson 3/23/2012
关于List<>,有一件有趣的事情需要注意。ForEach 和 Array.ForEach 是它们实际上并没有在内部使用 foreach 构造。它们都使用普通的 for 循环。也许这是一个性能问题(diditwith.net/2006/10/05/PerformanceOfForeachVsListForEach.aspx)。但鉴于 Array 和 List 都实现了 ForEach 方法,令人惊讶的是,如果不是 IEnumerable,它们至少没有为 IList<> 实现扩展方法<>。
17赞 Aaron Powell 9/19/2008 #3

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

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

不错的小扩展方法。

评论

2赞 Amy B 9/19/2008
col 可能为 null...你也可以检查一下。
0赞 Aaron Powell 12/30/2008
是的,我确实检查了我的图书馆,我只是在那里快速完成时错过了它。
0赞 Cameron MacFarland 3/21/2010
我发现有趣的是,每个人都检查操作参数是否为 null,但从不检查 source/col 参数。
2赞 oɔɯǝɹ 6/11/2013
我发现有趣的是,每个人都检查参数是否为 null,而不检查异常结果......
0赞 Roger Willcocks 7/22/2015
一点也不。Null Ref 异常不会告诉您出错的参数的名称。您还可以在循环中进行检查,以便出于同样的原因抛出特定的 Null Ref,说 col[index#] 为 null
228赞 Coincoin 9/19/2008 #4

语言中已经包含了一个语句,该语句在大多数时候都完成了这项工作。foreach

我讨厌看到以下内容:

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

而不是:

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

在大多数情况下,后者更清晰、更易于阅读,尽管打字时间可能更长一些。

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

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

  • 类型检查:foreach 在运行时完成,在编译时完成(大加!ForEach()
  • 调用委托的语法确实要简单得多:objects。ForEach(做某事);
  • ForEach() 可以被链接:尽管这种功能的邪恶/有用性是可以讨论的。

这些都是这里许多人提出的好观点,我明白为什么人们错过了这个功能。我不介意 Microsoft 在下一次框架迭代中添加标准的 ForEach 方法。

评论

50赞 mancaus 10/12/2008
这里的讨论给出了答案:forums.microsoft.com/MSDN/......基本上,决定保持扩展方法在功能上的“纯粹”。ForEach 在使用扩展方法时会鼓励副作用,这不是本意。
22赞 Jay Bazuzi 2/3/2009
如果你有 C 背景,“foreach”可能看起来更清楚,但内部迭代更清楚地说明了你的意图。这意味着你可以做一些事情,比如“序列”。AsParallel() 中。ForEach(...)'。
13赞 Richard Poole 1/11/2011
我不确定你关于类型检查的观点是否正确。 如果 Enumerable 已参数化,则在编译时进行类型检查。它只缺少对非泛型集合的编译时类型检查,就像假设的扩展方法一样。foreachForEach
60赞 Jacek Gorgoń 2/28/2012
扩展方法在带有点表示法的链式 LINQ 中非常有用,例如:.语法简单得多,foreach() 中没有冗余局部变量或长括号的屏幕污染。col.Where(...).OrderBy(...).ForEach(...)
48赞 Jim Balter 2/27/2017
“我讨厌看到以下内容”——问题不在于你的个人偏好,你通过添加无关的换行符和一对无关的大括号使代码更加可憎。不是那么可恨.谁说这是一个清单?显而易见的用例是在链的末端;使用 foreach 语句,您必须将整个链放在 之后,并将要执行的操作放在块内,这远不那么自然或一致。list.ForEach(item => item.DoSomething())in
3赞 TraumaPony 9/19/2008 #5

如果你有 F#(将在下一版本的 .NET 中),则可以使用

Seq.iter doSomething myIEnumerable

评论

10赞 Scott Hutchinson 8/17/2017
因此,Microsoft 选择不为 C# 和 VB 中的 IEnumerable 提供 ForEach 方法,因为它不是纯粹的函数式方法;然而,他们确实用他们更实用的语言 F# 提供了它(名称 iter)。他们的逻辑暗示了我。
2赞 user18784 9/19/2008 #6

是我还是List<T>。Foreach 几乎被 Linq 淘汰了。 最初有

foreach(X x in Y) 

其中 Y 只需是 IEnumerable(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 一直是 Vistor 模式的实现,(参见 Gamma、Helm、Johnson、Vlissides 的设计模式)。

当然,在 3.5 中,我们可以改用 Lambda 来达到相同的效果,例如尝试 http://dotnet-developments.blogs.techtarget.com/2008/09/02/iterators-lambda-and-linq-oh-my/

评论

1赞 Morgan Cheng 10/24/2008
我相信为“foreach”生成的代码应该有一个try-finally块。因为 T 的 IEnumerator 继承自接口 IDisposable。因此,在生成的 finally 块中,T 实例的 IEnumerator 应为 Disposed。
8赞 Scott Dorman 9/19/2008 #7

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

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

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

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

关于 ForEach 的具体论点是,基于对扩展方法的约束(即扩展方法永远不会重写具有相同签名的继承方法),可能会出现这样一种情况,即自定义扩展方法在隐含 IEnumerable 的所有类上都可用>但 List> 除外。当方法开始根据是否调用扩展方法或继承方法而表现出不同的行为时,这可能会导致混淆。<T<T

2赞 Paco 9/19/2008 #8

当您想要返回某些内容时,可以使用 select。 如果不这样做,可以先使用 ToList,因为您可能不想修改集合中的任何内容。

评论

0赞 Jim Balter 2/27/2017
a) 这不是问题的答案。b) ToList 需要额外的时间和内存。
22赞 Chris Zwiryk 9/19/2008 #9

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

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

评论

0赞 Mark T 3/19/2009
很棒的扩展,但你能添加一些文档吗?返回值的预期用途是什么?为什么索引是 var 而不是专门的 int?示例用法?
0赞 Chris Zwiryk 3/20/2009
标记:返回值是列表中的元素数。由于隐式类型化,Index 专门类型化为 int。
0赞 Eric Smith 5/16/2009
@Chris,根据上面的代码,返回值是列表中最后一个值的从零开始的索引,而不是列表中的元素数。
0赞 MartinStettner 8/27/2009
@Chris,不,它不是:索引在取值后递增(后缀递增),因此在处理第一个元素后,索引将为 1,依此类推。所以返回值实际上是元素计数。
1赞 Michael Blackburn 3/21/2014
@MartinStettner Eric Smith 在 5 月 16 日的评论来自早期版本。他在 5 月 18 日更正了代码以返回正确的值。
45赞 mancaus 10/12/2008 #10

这里的讨论给出了答案:

实际上,我目睹的具体讨论实际上取决于功能纯度。在表达式中,经常假设没有副作用。拥有 ForEach 特别会带来副作用,而不仅仅是忍受它们。-- Keith Farmer (合伙人)

基本上,决定保持扩展方法在功能上的“纯粹”。在使用 Enumerable 扩展方法时,ForEach 会鼓励副作用,这不是本意。

评论

7赞 Michael Freidgeim 7/8/2012
另请参阅 blogs.msdn.com/b/ericlippert/archive/2009/05/18/...。这样做违反了所有其他序列运算符所基于的函数式编程原则。显然,调用此方法的唯一目的是引起副作用
15赞 Jim Balter 10/10/2018
这种推理完全是虚假的。用例是需要副作用......如果没有 ForEach,则必须编写 foreach 循环。ForEach的存在不会鼓励副作用,也没有副作用也不会减少副作用。
0赞 Captain Prinny 11/21/2019
鉴于编写扩展方法是多么简单,从字面上看,没有理由说它不是语言标准。
2赞 Vapid 3/29/2021
@MichaelFreidgeim的存档现已失效链接:web.archive.org/web/20151205151706/http://blogs.msdn.com/b/...
17赞 Jay Bazuzi 10/20/2008 #11

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

优点

易于理解 - 读者只需要知道 C# 附带了什么,而不需要任何其他扩展方法。

语法噪声非常温和(只添加了一些无关的代码)。

通常不会花费额外的内存,因为无论如何,本地人都必须实现整个集合。.ForEach()

缺点

操作顺序并不理想。我宁愿意识到一个元素,然后采取行动,然后重复。此代码首先实现所有元素,然后按顺序对它们执行操作。

如果意识到列表引发了异常,则永远无法对单个元素执行操作。

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

评论

1赞 Jim Balter 2/27/2017
a) 这甚至不是问题的答案。b) 如果预先提到,虽然没有方法,但有方法,这个回答会更清楚。c) “通常不会花费额外的内存,因为原生 .无论如何,ForEach() 必须实现整个集合“——完全是胡说八道。下面是 C# 将提供的 Enumerable.ForEach<T> 的实现:Enumerable.ForEach<T>List<T>.ForEachpublic static void ForEach<T>(this IEnumerable<T> list, Action<T> action) { foreach (T item in list) action(item); }
60赞 Jay Bazuzi 10/20/2008 #12

你可以写这个扩展方法:

// 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# 库有太大的区别;不熟悉扩展方法的读者将不知道该如何编写代码。

评论

1赞 jjnguy 12/10/2010
如果写成这样,您的第一个函数可以在没有 'realize' 方法的情况下使用:{foreach (var e in source) {action(e);} return source;}
4赞 rasmusvhansen 2/22/2013
难道不能只使用 Select 而不是 Apply 方法吗?在我看来,将动作应用于每个元素并生成它正是 Select 所做的。例如numbers.Select(n => n*2);
1赞 Dr Rob Lang 10/22/2013
我认为 Apply 比为此目的使用 Select 更具可读性。在阅读 Select 语句时,我将其读作“从内部获取一些东西”,而 Apply 是“做一些工作”。
2赞 NetMage 1/19/2018
@rasmusvhansen 实际上,说将函数应用于每个元素并返回函数的值,其中表示将 an 应用于每个元素并返回原始元素。Select()Apply()Action
3赞 Jim Balter 10/10/2018
这相当于Select(e => { action(e); return e; })
4赞 diehard2 11/1/2008 #13

@Coincoin

foreach 扩展方法的真正强大之处在于 的可重用性,而无需向代码添加不必要的方法。假设你有 10 个列表,并且你想对它们执行相同的逻辑,并且相应的函数不适合你的类,并且不会被重用。您可以将所有逻辑保存在一个地方(.因此,数十行被替换为Action<>Action<>

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

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

等。。。

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

评论

0赞 Cameron MacFarland 11/3/2008
foreach (List1.Concat(List2) 中的 var p)。Concat(List3)) { ...做事... }
0赞 spoulson 1/23/2010
如果它像 一样简单,则省略 for 速记: 。f(p){ }for (var p in List1.Concat(List2).Concat(List2)) f(p);
-1赞 Squirrel #14

目前还没有人指出 ForEach<T> 会导致编译时类型检查,其中 foreach 关键字在运行时进行检查。

在代码中使用了这两种方法的地方进行了一些重构后,我赞成.ForEach,因为我必须寻找测试失败/运行时失败才能找到 foreach 问题。

评论

5赞 Richard Poole 1/11/2011
真的是这样吗?我看不出编译时检查如何减少。当然,如果您的集合是非泛型 IEnumerable,则循环变量将是一个 ,但对于假设的扩展方法也是如此。foreachobjectForEach
1赞 Jim Balter 2/27/2017
这在接受的答案中有所提及。然而,这完全是错误的(上面的理查德·普尔是正确的)。
2赞 Kirill Osenkov 2/2/2009 #15

我写了一篇关于它的博客文章:http://blogs.msdn.com/kirillosenkov/archive/2009/01/31/foreach.aspx

如果你想在 .NET 4.0 中看到此方法,可以在此处投票: http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=279093

评论

0赞 knocte 4/6/2020
这曾经实施过吗?我想不是,但是有没有其他 URL 可以替换那张票?MS connect 已停产
0赞 Kirill Osenkov 4/25/2020
这没有实现。考虑在 github.com/dotnet/runtime/issues 上提交新问题
2赞 Neil 11/12/2009 #16

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

7赞 Martijn 2/21/2013 #17

您可以使用 (chainable, but lazyed evaluate ),首先执行操作,然后返回身份(或者如果您愿意,可以返回其他内容)Select

IEnumerable<string> people = new List<string>(){"alica", "bob", "john", "pete"};
people.Select(p => { Console.WriteLine(p); return p; });

您需要确保它仍然被评估,无论是使用(枚举 afaik 的最便宜的操作)还是您无论如何都需要的其他操作。Count()

不过,我很想看到它被引入标准库:

static IEnumerable<T> WithLazySideEffect(this IEnumerable<T> src, Action<T> action) {
  return src.Select(i => { action(i); return i; } );
}

然后,上面的代码实际上等同于 foreach,但懒惰且可链接。people.WithLazySideEffect(p => Console.WriteLine(p))

评论

0赞 Alex KeySmith 2/14/2014
太棒了,它从来没有点击过返回的选择会像这样工作。方法语法的一个很好的用法,因为我认为你不能用表达式语法来做到这一点(因此我以前没有这样想过)。
0赞 Jim Balter 10/10/2018
@AlexKeySmith 如果 C# 有一个序列运算符,你可以用表达式语法来做到这一点,就像它的前身一样。但 ForEach 的全部意义在于它采用 void 类型的操作,并且您不能在 C# 的表达式中使用 void 类型。
0赞 Matthias Burger 9/25/2019
恕我直言,现在它不再做它应该做的事情了。 这绝对是 OP 要求的解决方案 - 但在主题可读性方面:没有人会期望 select 确实执行函数。Select
0赞 Martijn 10/7/2019
@MatthiasBurger 如果没有做它应该做的事情,那就是 的实现有问题,而不是调用 的代码的问题,它对执行什么没有影响。SelectSelectSelectSelect
6赞 Dave Clausen 1/2/2014 #18

请注意,MoreLINQ NuGet 提供了你要查找的扩展方法(以及执行委托并生成其结果的方法)。看:ForEachPipe

2赞 fredefox 2/17/2016 #19

我想扩展一下阿库的回答

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

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

评论

1赞 Jim Balter 10/10/2018
这与Select(x => { f(x); return x;})
4赞 McKay 10/14/2018 #20

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

  • 没有(和测试...)一个功能比有一个功能要少得多。
  • 它并不是很短(有一些传递函数的情况,但这不是主要用途)。
  • 它的目的是产生副作用,这不是 linq 的意义所在。
  • 为什么有另一种方法可以做与我们已经拥有的功能相同的事情?(foreach 关键字)

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

0赞 Liran Barniv 5/18/2021 #21

我的版本是一个扩展方法,允许您在 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;
        });
    }
}