为什么.ForEach()在IList <T>而不是IEnumerable <T>?

可能重复:
为什么IEnumerable接口上没有ForEach扩展方法?

我注意到在编写LINQ-y代码时,.ForEach()是一个很好用的习惯用法。 例如,下面是一段代码,它接受以下input,并生成这些输出:

{ "One" } => "One" { "One", "Two" } => "One, Two" { "One", "Two", "Three", "Four" } => "One, Two, Three and Four"; 

和代码:

 private string InsertCommasAttempt(IEnumerable<string> words) { List<string> wordList = words.ToList(); StringBuilder sb = new StringBuilder(); var wordsAndSeparators = wordList.Select((string word, int pos) => { if (pos == 0) return new { Word = word, Leading = string.Empty }; if (pos == wordList.Count - 1) return new { Word = word, Leading = " and " }; return new { Word = word, Leading = ", " }; }); wordsAndSeparators.ToList().ForEach(v => sb.Append(v.Leading).Append(v.Word)); return sb.ToString(); } 

注意在第二行到最后一行的.ForEach()之前插入的.ToList()。

为什么.ForEach()不能作为IEnumerable的扩展方法使用? 像这样的例子,这似乎很奇怪。

提前致谢,

戴夫

因为ForEach(Action)IEnumerable<T>存在之前就存在了。

由于没有添加其他扩展方法,因此可以假设C#devise人员认为这是一个糟糕的devise,并且倾向于使用foreach构造。


编辑:

如果你想要的话,你可以创build自己的扩展方法,它不会覆盖List<T>的扩展方法,但是它将适用于任何其他实现了IEnumerable<T>

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

据Eric Lippert介绍,这主要是出于哲学原因 。 你应该阅读整篇文章,但是就我而言,这里的要点是:

我在哲学上反对提供这样一种方法,原因有两个。

第一个原因是这样做违反了所有其他序列操作符所基于的函数式编程原则。 显然,调用这种方法的唯一目的是引起副作用。

expression式的目的是计算一个值,而不是引起副作用。 声明的目的是引起副作用。 这个东西的调用网站看起来非常像一个expression式(虽然,诚然,由于该方法是无效的返回,expression式只能用于“语句expression式”上下文)。

对我来说,做一个唯一的序列运算符只是对副作用有用,这并不是一个好的方法。

第二个原因是这样做增加了语言的新代表权。

因为IEnumerable上的ForEach()对于每个循环来说都是正常的:

 for each T item in MyEnumerable { // Action<T> goes here } 

我只是在这里猜测,但将foreach放在IEnumerable上会使其操作产生副作用。 没有一个“可用的”扩展方法会导致副作用,所以把一个像foreach这样的命令性的方法放在那里会让我猜的api变得浑浊。 另外,foreach会初始化懒惰的集合。

就我个人而言,我一直在试图添加我自己的,只是为了保持与副作用分开的副作用免费function的诱惑。

ForEach不在IList上,它在List上。 你在示例中使用了具体的List。

我真的不知道为什么.ForEach(动作)不包括IEnumerable,但是,对,错或无动于衷,这是它的方式…

但是,我想强调其他评论中提到的性能问题。 根据您如何遍历集合,性能会受到影响。 虽然相对较小,但是它确实存在。 这是一个令人难以置信的快速和草率的代码片段来显示关系…只需要一分钟左右的时间来贯穿。

 class Program { static void Main(string[] args) { Console.WriteLine("Start Loop timing test: loading collection..."); List<int> l = new List<int>(); for (long i = 0; i < 60000000; i++) { l.Add(Convert.ToInt32(i)); } Console.WriteLine("Collection loaded with {0} elements: start timings",l.Count()); Console.WriteLine("\n<===============================================>\n"); Console.WriteLine("foreach loop test starting..."); DateTime start = DateTime.Now; //l.ForEach(x => l[x].ToString()); foreach (int x in l) l[x].ToString(); Console.WriteLine("foreach Loop Time for {0} elements = {1}", l.Count(), DateTime.Now - start); Console.WriteLine("\n<===============================================>\n"); Console.WriteLine("List.ForEach(x => x.action) loop test starting..."); start = DateTime.Now; l.ForEach(x => l[x].ToString()); Console.WriteLine("List.ForEach(x => x.action) Loop Time for {0} elements = {1}", l.Count(), DateTime.Now - start); Console.WriteLine("\n<===============================================>\n"); Console.WriteLine("for loop test starting..."); start = DateTime.Now; int count = l.Count(); for (int i = 0; i < count; i++) { l[i].ToString(); } Console.WriteLine("for Loop Time for {0} elements = {1}", l.Count(), DateTime.Now - start); Console.WriteLine("\n<===============================================>\n"); Console.WriteLine("\n\nPress Enter to continue..."); Console.ReadLine(); } 

不要挂在这太多,虽然。 性能是应用程序devise的货币,但除非您的应用程序遇到导致可用性问题的实际性能打击,否则将重点放在可维护性和重用性的编码上,因为时间是现实生活中业务项目的货币。

ForEach在具体的类List<T>

只是一个猜测,但List可以迭代其项目而不创build一个枚举器:

 public void ForEach(Action<T> action) { if (action == null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match); } for (int i = 0; i < this._size; i++) { action(this._items[i]); } } 

这可以导致更好的性能。 IEnumerable,你没有select使用一个普通的for循环。

IEnumerable<T>上被称为“Select”我很开明,谢谢。

LINQ遵循拉模型,所有的(扩展)方法都应该返回IEnumerable<T> ,除了ToList()ToList()在那里结束拉链。

ForEach()来自推模式世界。

正如塞缪尔所指出的,你仍然可以编写自己的扩展方法来完成这个任务。