关于c#:为什么IList上的.ForEach()< T>

Why is .ForEach() on IList<T> and not on IEnumerable<T>?

本问题已经有最佳答案,请猛点这里访问。

Possible Duplicate:
Why is there not a ForEach extension method on the IEnumerable interface?

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

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

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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上的扩展方法?举个这样的例子,它看起来很奇怪。


据埃里克·利珀特称,这主要是出于哲学原因。你应该阅读整篇文章,但我认为要点是:

I am philosophically opposed to
providing such a method, for two
reasons.

The first reason is that doing so
violates the functional programming
principles that all the other sequence
operators are based upon. Clearly the
sole purpose of a call to this method
is to cause side effects.

The purpose of an expression is to
compute a value, not to cause a side
effect. The purpose of a statement is
to cause a side effect. The call site
of this thing would look an awful lot
like an expression (though,
admittedly, since the method is
void-returning, the expression could
only be used in a"statement
expression" context.)

It does not sit well with me to make
the one and only sequence operator
that is only useful for its side
effects.

The second reason is that doing so
adds zero new representational power
to the language.


因为ForEach(Action)早于IEnumerable存在。

因为它没有与其他扩展方法一起添加,所以可以假定C设计人员认为它是一个糟糕的设计,并且更喜欢foreach结构。

编辑:

如果您希望创建自己的扩展方法,它不会覆盖List的方法,但它将适用于实现IEnumerable的任何其他类。

1
2
3
4
5
6
7
8
public static class IEnumerableExtensions
{
  public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
  {
    foreach (T item in source)
      action(item);
  }
}


因为IEnumerable上的ForEach()对于这样的每个循环只是一个正常值:

1
2
3
4
for each T item in MyEnumerable
{
    // Action<T> goes here
}


我只是在猜测,但是将foreach放在ienumerable上会对其进行操作,从而产生副作用。"可用"的扩展方法都不会产生副作用,在上面放置一个像foreach这样的命令式方法会弄脏API。此外,foreach将初始化lazy集合。

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


老实说,我不知道为什么.foreach(action)不包含在IEnumerable中,但是,正确、错误或冷漠,这就是它的方式…

不过,我想强调一下其他评论中提到的性能问题。性能会受到影响,这取决于您如何循环遍历集合。它相对较小,但它确实存在。这里有一个非常快速和草率的代码片段来显示关系…只需要一分钟左右的时间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
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("
<===============================================>
"
);
        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("
<===============================================>
"
);
        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("
<===============================================>
"
);

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

        Console.WriteLine("

Press Enter to continue..."
);
        Console.ReadLine();
    }

不过,不要太在意这个问题。性能是应用程序设计的货币,但除非您的应用程序遇到实际的性能问题,从而导致可用性问题,否则请将重点放在可维护性和重用性的编码上,因为时间是实际业务项目的货币…


foreach不在列表中。您在示例中使用了具体的列表。


linq遵循pull模型,其所有(扩展)方法都应返回IEnumerableToList()除外。ToList()用于结束牵引链。

ForEach()来自推模型世界。

正如塞缪尔所指出的那样,您仍然可以编写自己的扩展方法来实现这一点。


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

1
2
3
4
5
6
7
8
9
10
11
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循环的选项。


IEnumerable上称为"select"(选择)我很开明,谢谢。


foreach在具体类List中实现。