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

时间:2008-09-19 11:49:54

标签: c# .net vb.net extension-methods

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

为什么ForEach类中没有Enumerable扩展方法?还是在任获得ForEach方法的唯一类是List<>。有没有理由错过(表演)?

22 个答案:

答案 0 :(得分:181)

在大多数情况下,该语言中已包含foreach语句。

我不想看到以下内容:

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

而不是:

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

后者更清晰,更容易阅读在大多数情况下,虽然输入可能要长一点。

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

以下是声明与方法之间的主要区别:

  • 类型检查:foreach在运行时完成,ForEach()在编译时(Big Plus!)
  • 调用委托的语法确实要简单得多:objects.ForEach(DoSomething);
  • ForEach()可以被链接:虽然这种功能的邪恶/有用性可供讨论。

这些都是很多人在这里提出的重点,我可以看出为什么人们会错过这个功能。我不介意微软在下一个框架迭代中添加标准的ForEach方法。

答案 1 :(得分:70)

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

但是,如果你真的错过这个小功能,你可以推出自己的版本

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

答案 2 :(得分:48)

您可以编写此扩展方法:

// 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(...);

<强>缺点

在你做一些强制迭代之前,它实际上不会做任何事情。因此,不应将其称为.ForEach()。你可以在最后写.ToList(),或者你也可以编写这个扩展方法:

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

    return source;
}

这可能与运送C#库有太大不同;不熟悉您的扩展方法的读者将不知道如何编写代码。

答案 3 :(得分:32)

The discussion here给出答案:

  

实际上,我目睹的具体讨论确实取决于功能纯度。在表达式中,经常假设没有副作用。拥有ForEach是特别邀请副作用,而不仅仅是忍受它们。 - Keith Farmer(合伙人)

基本上,决定保持扩展方法在功能上“纯粹”。当使用Enumerable扩展方法时,ForEach会鼓励副作用,这不是意图。

答案 4 :(得分:16)

虽然我同意在大多数情况下使用内置foreach构造更好,但我发现在ForEach&lt;&gt;上使用此变体。扩展比我自己在常规foreach中管理索引要好一些:

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

会给你:

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

答案 5 :(得分:14)

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

<强>优点

易于理解 - 读者只需知道C#附带的内容,而不是任何其他扩展方法。

句法噪音非常温和(只增加了一些极端的代码)。

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

<强>缺点

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

如果实现列表会引发异常,则永远不会对单个元素执行操作。

如果枚举是无限的(就像自然数字一样),那你就不走运了。

答案 6 :(得分:12)

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

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);
    }
}

很好的小扩展方法。

答案 7 :(得分:7)

因此,有很多关于ForEach扩展方法不适合的事实的评论,因为它不返回类似LINQ扩展方法的值。虽然这是一个事实陈述,但并非完全正确。

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

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

然而,仅仅因为LINQ是使用扩展方法实现的,并不意味着扩展方法必须以相同的方式使用并返回一个值。编写扩展方法以公开不返回值的常用功能是完全有效的用法。

关于ForEach的具体论点是,基于对扩展方法的约束(即扩展方法将从不覆盖具有相同签名的继承方法),可能存在这样的情况:自定义扩展方法可用于所有阻碍IEnumerable <T&gt;的类。列表<T&gt;除外。当方法开始表现不同时,这会导致混淆,具体取决于是否调用了扩展方法或继承方法。

答案 8 :(得分:6)

您可以使用(可链接,但延迟评估)Select,首先执行您的操作,然后返回身份(或者您喜欢的其他内容)

IEnumerable<string> people = new List<string>(){"alica", "bob", "john", "pete"};
people.Select(p => { Console.WriteLine(p); return p; });

您需要确保仍然使用Count()(枚举afaik的最便宜的操作)或者您需要的其他操作来评估它。

我很乐意看到它带入标准库:

static IEnumerable<T> WithLazySideEffect(this IEnumerable<T> src, Action<T> action) {
  return src.Select(i => { action(i); return i; } );
}

上面的代码然后变成people.WithLazySideEffect(p => Console.WriteLine(p)),它实际上等同于foreach,但是懒惰和可链接。

答案 9 :(得分:4)

请注意, MoreLINQ NuGet提供了您正在寻找的ForEach扩展方法(以及执行委托并生成结果的Pipe方法)。参见:

答案 10 :(得分:4)

@Coincoin

foreach扩展方法的真正强大功能包括Action<>的可重用性,而无需为代码添加不必要的方法。假设您有10个列表,并且您希望对它们执行相同的逻辑,并且相应的函数不适合您的类,并且不会重复使用。您可以将所有逻辑保存在一个位置(Action<>,而不是拥有10个for循环,或者显然是不属于的辅助函数的通用函数。所以,将数十行替换为

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

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

等...

逻辑在一个地方,你没有污染你的班级。

答案 11 :(得分:2)

如果你有F#(将在.NET的下一个版本中),你可以使用

Seq.iter doSomething myIEnumerable

答案 12 :(得分:2)

部分原因是语言设计者从哲学角度不同意。

  • 不具有(和测试...)功能要比具有功能少。
  • 它并没有真正缩短(在某些传递函数的情况下,但这不是主要用途)。
  • 目的是要产生副作用,这不是linq所要解决的。
  • 为什么还有另一种方法可以与我们已经拥有的功能做相同的事情? (foreach关键字)

https://blogs.msdn.microsoft.com/ericlippert/2009/05/18/foreach-vs-foreach/

答案 13 :(得分:2)

我想扩展Aku's answer

如果你想调用方法的唯一目的是它的副作用而不先迭代整个可枚举,你可以使用它:

private static IEnumerable<T> ForEach<T>(IEnumerable<T> xs, Action<T> f) {
    foreach (var x in xs) {
        f(x); yield return x;
    }
}

答案 14 :(得分:2)

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

答案 15 :(得分:2)

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

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

答案 16 :(得分:2)

大多数LINQ扩展方法都返回结果。 ForEach不适合这种模式,因为它什么都不返回。

答案 17 :(得分:2)

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

答案 18 :(得分:2)

是我还是名单&lt; T&gt; .Foreach几乎已经被Linq淘汰了。 最初有

foreach(X x in Y) 

其中Y只需要IEnumerable(Pre 2.0),并实现GetEnumerator()。 如果查看生成的MSIL,您可以看到它与

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

    Console.WriteLine(i);
}

(请参阅http://alski.net/post/0a-for-foreach-forFirst-forLast0a-0a-.aspx了解MSIL)

然后在DotNet2.0中出现了Generics和List。 Foreach一直觉得我是Vistor模式的实现,(参见Gamma,Helm,Johnson,Vlissides的Design Patterns)。

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

答案 19 :(得分:1)

要使用Foreach,您的列表应加载到主内存中。但由于IEnumerable的延迟加载性质,它们不会在IEnumerable中提供ForEach。

但是,通过急切加载(使用ToList()),您可以将列表加载到内存中并利用ForEach。

答案 20 :(得分:0)

还没有人指出ForEach&lt; T&gt;导致编译时类型检查foreach关键字在运行时检查的位置。

在代码中使用了两种方法时进行了一些重构,我赞成.ForEach,因为我不得不寻找测试失败/运行时失败来找到foreach问题。

答案 21 :(得分:0)

我的版本是一种扩展方法,允许您在 T 的 IEnumerable 上使用 ForEach

public static class EnumerableExtension
{
        public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
    {
        source.All(x =>
        {
            action.Invoke(x);
            return true;
        });
    }
}