关于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上的扩展方法?举个这样的例子,它看起来很奇怪。

  • 它甚至不包括在IList接口中,而是在列表中
  • 问题是IEnumerable和可能的IList是懒惰的,因此您仍然需要在.foreach之后调用.tolist()或.toarray(),以确保实际迭代这些项。如果不使用这些值,那么IEnumerable的懒惰计算将导致您认为前臂未被抢占。这就是为什么还存在array.foreach的原因,因为一旦调用了toarray(),编译器就可以确保列表已被迭代。对于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.

  • 如果你自己的个人编程偏好会影响到数百万其他人,这不是件好事吗?这个"埃里克·利珀特"运用的力量…
  • 是的,如果我们每个人都能设计出数百万人使用的编译器的话:)
  • 但是我们没有得到索引,我们被简化为一个正则for循环。对吗?
  • 这是一个很好的解释,解释了为什么这个方法不存在,以及为什么你可能会三思而后行。


因为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方法
  • 不,但这表明他们觉得这个方法是错误的,更喜欢foreach结构。
  • 不管是这个还是上面的那个都足以回答我的问题。谢谢你的建议。
  • foreach循环内的action()调用应以item为参数,不应以T为参数。


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

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

  • 嗯,我可以看到在前臂中嵌入lambda而不是这样做的吸引力。但这似乎只是一个小问题。
  • 不过,能像在列表中那样以内联方式进行这样的操作难道不是很好吗?我过去也曾想知道这一点,但从未想过要提出来。
  • 我想问题是,他们为什么不定义.foreach作为IEnumerable接口的一部分?您可以对其他列表类型进行foreach,也可以使用.foreach()方法,我看不出有什么区别。
  • 是的,这也是我的想法。
  • 唯一真正的答案是:因为。无论出于什么原因,设计师决定不添加IEnumerable。但如果你愿意的话,这并不能阻止你自己实现它。
  • 是的,如果你添加一个扩展方法,这很容易,但是你要做的只是在它里面的前臂,哪种方法会破坏目标,但是无论如何。:)
  • @巴拉巴斯特:"你可以在列表类型前搜索"…这是因为这些列表类型满足实现IEnumerable的要求。


我只是在猜测,但是将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循环的选项。

  • 在几乎所有情况下,性能损失都可以忽略不计。而且duh不能使用for循环,因为IEnumerable没有索引的概念。
  • 它可以起到作用。我曾经遇到过这样的情况:从"foreach"转到"for"会导致10的加速,仅仅是因为垃圾收集器不再需要收集枚举器。
  • 我打电话给BS。要么出示证据,要么你做了一些非常错误的循环。
  • @Rauhotz你必须有一个非常糟糕的枚举器
  • 什么该死的枚举器?它是从list返回的枚举器。getEnumerator()是一个对象,之后必须由gc创建和收集。在多线程应用程序中使用它,您将看到GC占用了90%的CPU时间。当使用listforeach时,将不会创建任何对象,这会产生影响。


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

  • 阿克!我错了吗?启发我!
  • 不完全正确-在上面的示例中,wordsandseparaters.select(v=>sb.append(v.leading).append(v.word));不提供您所期望的,因为管道。您必须说:wordsandseparaters.select(v=>sb.append(v.leading).append(v.word)).tolist();
  • foreach执行的操作是一个委托,它接受TS并返回void;因此,foreach返回void。select接受一个func,它是一个接受ts并返回tresults的委托;因此,select返回一个ienumerable


foreach在具体类List中实现。

  • 向接口添加扩展方法是绝对可能的。
  • 雷克斯再次带着负面评论去救援!是的,我的跟踪者回来了
  • 这是什么负面评论?
  • @雷克斯:既然他编辑了它,你的评论看起来是错的。但它最初说您不能在接口上使用扩展方法,这是非常错误的。
  • 我承认我草率地说了一句不正确的话并改正了它。我通常避免使用扩展方法,因为它们以与运算符重载相同的方式使代码复杂化…他们很难"知道"他们在那里。所以我没有使用它们的经验。
  • 您可以向接口添加扩展方法:IEnumerable本身有许多很好的示例。
  • @塞缪尔,他似乎也混淆了"在问题上是错的,我也被吸引到"和"跟踪";)
  • 我收回了投反对票,因为答案不再是正确的。顺便说一句,我试着帮助你,而不是出去找你。承诺。
  • 呃,*不正确。
  • 我不在乎投票结果的下降。但是有没有编辑的"礼仪"?我经常编辑。我可以看到有人复制/粘贴正确的答案只是为了得到一些简单的赞成票。
  • @越轨我从没见过这种事发生。它可能会不时出现,但我想它是非常罕见的。很容易看出是谁先说的,如果你只关心代表点,足以试图窃取他们,你也足够关心,如果他们抓住你,你也不想让一吨选票下跌的愤怒。