55

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

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

{ "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();
}

注意倒数第二行.ToList()之前的插入。.ForEach()

为什么它.ForEach()不能作为扩展方法使用IEnumerable<T>?像这样的例子,它看起来很奇怪。

4

10 回答 10

40

因为ForEach(Action)以前IEnumerable<T>存在过。

由于它没有与其他扩展方法一起添加,因此可以假设 C# 设计人员认为这是一个糟糕的设计,并且更喜欢该foreach构造。


编辑:

如果您愿意,您可以创建自己的扩展方法,它不会覆盖 a 的方法,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);
  }
}
于 2009-04-28T22:54:04.483 回答
40

根据 Eric Lippert 的说法,这主要是出于哲学原因。您应该阅读整篇文章,但就我而言,这是要点:

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

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

表达式的目的是计算一个值,而不是产生副作用。声明的目的是产生副作用。这个东西的调用点看起来很像一个表达式(虽然,不可否认,由于该方法是返回无效的,所以该表达式只能在“语句表达式”上下文中使用。)

制作一个唯一的仅对其副作用有用的序列运算符并不适合我。

第二个原因是这样做会给语言增加零新的表示能力。

于 2009-08-18T21:55:52.547 回答
5

因为ForEach()在 IEnumerable 上对于每个循环来说都是正常的,如下所示:

for each T item in MyEnumerable
{
    // Action<T> goes here
}
于 2009-04-28T22:50:54.117 回答
3

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

于 2009-04-28T22:56:14.183 回答
3

我只是在这里猜测,但将 foreach 放在 IEnumerable 上会使操作对它产生副作用。“可用”的扩展方法都不会引起副作用,我猜想在那里放置像 foreach 这样的命令式方法会使 api 变得混乱。此外, foreach 将初始化惰性集合。

就个人而言,我一直在抵制只添加我自己的诱惑,只是为了将无副作用的功能与有副作用的功能分开。

于 2009-04-28T23:10:24.447 回答
0

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

ForEach()来自推模型世界。

正如 Samuel 所指出的,您仍然可以编写自己的扩展方法来执行此操作。

于 2009-04-28T23:17:18.657 回答
0

ForEach 在具体类中实现List<T>

于 2009-04-28T22:51:30.747 回答
0

只是一个猜测,但 List 可以在不创建枚举器的情况下迭代其项目:

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,您无法选择使用普通的 for 循环。

于 2009-04-28T22:58:04.803 回答
0

老实说,我不确定为什么 .ForEach(Action) 不包含在 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();
    }

不过不要太挂断这个。性能是应用程序设计的货币,但除非您的应用程序正在经历导致可用性问题的实际性能冲击,否则请专注于可维护性和重用编码,因为时间是现实生活中业务项目的货币......

于 2009-11-19T18:59:10.753 回答
-1

它被称为“选择”IEnumerable<T> 我很开明,谢谢。

于 2009-04-28T22:52:12.430 回答