为什么IEnumerable上没有ForEach扩展方法?

时间:2020-03-06 14:25:36  来源:igfitidea点击:

受到另一个问题的启发,询问缺少的" Zip"功能:

为什么Enumerable类中没有ForEach扩展方法?还是任何地方?唯一获得ForEach方法的类是List <>。是否有其丢失(性能)的原因?

解决方案

大多数LINQ扩展方法都返回结果。 ForEach不适合此模式,因为它不返回任何内容。

在LINQ之前添加了ForEach方法。如果添加ForEach扩展,由于扩展方法的限制,它将永远不会被List实例调用。我认为未添加它的原因是不干扰现有的。

但是,如果我们真的错过了这个小巧的功能,则可以推出自己的版本

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

我一直想知道自己,这就是为什么我总是随身携带这个:

public static void ForEach<T>(this IEnumerable<T> col, Action<T> action)
{
    if (action == null)
    {
        throw new ArgumentNullException("action");
    }
    foreach (var item in col)
    {
        action(item);
    }
}

不错的扩展方法。

语言中已经包含了一个foreach语句,该语句在大多数情况下会起作用。

我不愿看到以下内容:

list.ForEach( item =>
{
    item.DoSomething();
} );

代替:

foreach(Item item in list)
{
     item.DoSomething();
}

后者在大多数情况下更清晰易读,尽管键入时间可能更长一些。

但是,我必须承认我在这个问题上改变了立场。在某些情况下,ForEach()扩展方法确实很有用。

这是语句和方法之间的主要区别:

  • 类型检查:foreach在运行时完成,ForEach()在编译时(Big Plus!)
  • 调用委托的语法确实简单得多:objects.ForEach(DoSomething);
  • 可以将ForEach()链接起来:尽管这种功能的弊端/使用价值尚待讨论。

这些都是许多人在这里提出的要点,我可以理解为什么人们缺少此功能。我不介意Microsoft在下一个框架迭代中添加标准的ForEach方法。

如果我们有F(它将在.NET的下一版本中),则可以使用

Seq.iter doSomething myIEnumerable

是我还是List <T>。Linq几乎使Foreach过时了。
最初有

foreach(X x in Y)

其中Y只需是IEnumerable(Pre 2.0),并实现GetEnumerator()。

IEnumerator<int> enumerator = list.GetEnumerator();
while (enumerator.MoveNext())
{
    int i = enumerator.Current;

    Console.WriteLine(i);
}

如果查看生成的MSIL,我们会发现它与

(有关MSIL,请参见http://alski.net/post/0a-for-foreach-forFirst-forLast0a-0a-.aspx)

然后在DotNet2.0中出现了泛型和列表。我一直认为Foreach是Vistor模式的实现,(请参阅Gamma,Helm,Johnson和Vlissides的设计模式)。

当然,现在在3.5中,我们可以改为使用Lambda达到相同的效果,例如尝试
http://dotnet-developments.blogs.techtarget.com/2008/09/02/iterators-lambda-and-linq-oh-my/

因此,对于ForEach扩展方法不合适,因为它不像LINQ扩展方法那样返回值,这引起了很多评论。尽管这是事实陈述,但并非完全正确。

collection.Where(i => i.Name = "hello").Select(i => i.FullName);

LINQ扩展方法都返回一个值,因此可以将它们链接在一起:

但是,仅仅因为LINQ是使用扩展方法实现的,并不意味着必须以相同的方式使用扩展方法并返回值。编写扩展方法以暴露不返回值的通用功能是一种完全有效的用法。

关于ForEach的特定论点是,基于对扩展方法的限制(即,扩展方法将永远不会覆盖具有相同签名的继承方法),可能存在这样的情况:自定义扩展方法可用于所有阻碍实现的类IEnumerable&lt;T>,除了List&lt;T>。当方法根据调用扩展方法还是继承方法而开始表现不同时,这可能引起混乱。

当我们要返回某些内容时,可以使用select。
如果不这样做,则可以先使用ToList,因为我们可能不想修改集合中的任何内容。

public static int ForEach<T>(this IEnumerable<T> list, Action<int, T> action)
{
    if (action == null) throw new ArgumentNullException("action");

    var index = 0;

    foreach (var elem in list)
        action(index++, elem);

    return index;
}

在3.5中,所有添加到IEnumerable的扩展方法都支持LINQ(请注意,它们是在System.Linq.Enumerable类中定义的)。在这篇文章中,我解释了为什么foreach不属于LINQ:
现有的LINQ扩展方法类似于Parallel.For?

var people = new[] { "Moe", "Curly", "Larry" };
people.ForEach((i, p) => Console.WriteLine("Person #{0} is {1}", i, p));

虽然我同意在大多数情况下使用内置的foreach构造会更好,但我发现在ForEach <>扩展中使用此变体比在常规的`foreach'中管理索引要好一些我:

Person #0 is Moe
Person #1 is Curly
Person #2 is Larry

会给你:

Actually, the specific discussion I witnessed did in fact hinge over functional purity.  In an expression, there are frequently assumptions made about not having side-effects.  Having ForEach is specifically inviting side-effects rather than just putting up with them. -- Keith Farmer (Partner)

这里的讨论给出了答案:

基本上,决定使扩展方法在功能上保持"纯"。使用Enumerable扩展方法时,ForEach会产生副作用,这不是目的。

一种解决方法是编写.ToList()。ForEach(x => ...)

优点

易于理解的读者只需知道C#附带了什么,而无需任何其他扩展方法。

语法噪声非常轻微(仅添加了一些无关紧要的代码)。

通常不会花费额外的内存,因为无论如何本机的.ForEach()必须实现整个集合。

缺点

操作顺序并不理想。我宁愿意识到一个要素,然后对其采取行动,然后重复。该代码首先实现所有元素,然后依次对它们进行操作。

如果实现该列表引发异常,则我们永远不必对单个元素采取行动。

如果枚举是无限的(例如自然数),那么我们就不走运了。

// Possibly call this "Do"
IEnumerable<T> Apply<T> (this IEnumerable<T> source, Action<T> action)
{
    foreach (var e in source)
    {
        action(e);
        yield return e;
    }
}

我们可以编写以下扩展方法:

优点

MySequence
    .Apply(...)
    .Apply(...)
    .Apply(...);

允许链接:

缺点

// possibly call this "Realize"
IEnumerable<T> Done<T> (this IEnumerable<T> source)
{
    foreach (var e in source)
    {
        // do nothing
        ;
    }

    return source;
}

在我们执行强制迭代之前,它实际上不会做任何事情。因此,不应将其称为.ForEach()。我们可以在末尾编写.ToList(),或者也可以编写此扩展方法:

背离运输图书馆可能太重要了;不熟悉扩展方法的读者将不知道如何编写代码。

@钱币

Action<blah,blah> f = { foo };

List1.ForEach(p => f(p))
List2.ForEach(p => f(p))

foreach扩展方法的真正强大之处在于可在不向代码中添加不必要的方法的情况下,重复使用Action <>。假设我们有10个列表,并且想要对它们执行相同的逻辑,并且相应的函数不适合类,因此不会被重用。我们可以将所有逻辑放在一个位置(" Action <>"。),而不是使用十个for循环或者一个显然不属于辅助函数的通用函数,可以将所有逻辑都保留在一个位置。

等等...

逻辑合二为一,我们没有污染课堂。

还没有人指出ForEach <T>会导致编译时类型检查,其中foreach关键字是运行时检查的。

在对两种方法都在代码中使用的位置进行了一些重构之后,我偏爱.ForEach,因为我必须找出测试失败/运行时失败来查找foreach问题。

我写了一篇关于它的博客文章:
http://blogs.msdn.com/kirillosenkov/archive/2009/01/31/foreach.aspx

如果我们想在.NET 4.0中看到此方法,可以在这里投票:
http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=279093

零散数量不匹配