为什么IList上的.ForEach()< T>而不是IEnumerable< T>? [重复]

StackOverflow https://stackoverflow.com/questions/800151

  

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

我注意到在编写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();
}

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

为什么 .ForEach() IEnumerable&lt; T&gt; 上不能用作扩展方法?有了这样的例子,它看起来很奇怪。

有帮助吗?

解决方案

因为 ForEach(Action) 存在 IEnumerable&lt; T&gt; 之前。

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


编辑:

如果你想要你可以创建自己的扩展方法,它不会覆盖 List&lt; T&gt; 的方法,但它适用于实现 IEnumerable&lt; T&gt;的任何其他类。 ;

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的说法,这是出于哲学原因。你应该阅读整篇文章,但就我而言,这里是要点:

  

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

     

第一个原因就是这样做   违反了函数式编程   所有其他顺序的原则   运营商是基于。显然是   唯一目的是调用此方法   是造成副作用。

     

表达的目的是   计算一个值,而不是引起一个方面   影响。声明的目的是   引起副作用。通话网站   这件事情看起来很糟糕   像一个表达(虽然,   诚然,因为方法是   无效返回,表达式可以   仅用于&#8220;声明   表达&#8221;上下文。)

     

与我合作并不合适   唯一的序列运算符   这只对它的一方有用   的效果。

     

第二个原因就是这样做   增加零代表性能力   语言。

因为IEnumerable上的 ForEach()只是每个循环的正常,如下所示:

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

我只是在这里猜测,但将foreach放在IEnumerable上会使对它的操作产生副作用。没有“可用”的扩展方法会产生副作用,像foreach那样采用一种必要的方法会让我认为api很混乱。此外,foreach会初始化懒惰的集合。

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

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

老实说,我不知道为什么.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();
    }

尽管如此,不要因为太多而挂断电话。性能是应用程序设计的替代品,但除非您的应用程序遇到导致可用性问题的实际性能损失,否则请关注可维护性和重用的编码,因为时间是现实生活中的商业项目的货币......

ForEach在具体类 List&lt; T&gt;

中实现

只是猜测,但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循环。

它被称为“选择”在 IEnumerable&lt; T&gt; 上 我很开明,谢谢你。

LINQ遵循拉模型,其所有(扩展)方法都应返回 IEnumerable&lt; T&gt; ,但 ToList()除外。 ToList()用于结束拉链。

ForEach()来自推模型世界。

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top