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

Cam*_*and 368 .net c# vb.net extension-methods

受到另一个询问失踪问题的启发 Zip功能的:

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

Coi*_*oin 189

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

我不想看到以下内容:

list.ForEach( item =>
{
    item.DoSomething();
} );
Run Code Online (Sandbox Code Playgroud)

代替:

foreach(Item item in list)
{
     item.DoSomething();
}
Run Code Online (Sandbox Code Playgroud)

在大多数情况下,后者更清晰,更容易阅读,但输入可能要长一些.

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

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

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

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

  • 扩展方法在带有点符号的链LINQ中非常有用,例如:``col.Where(...).OrderBy(...).ForEach(...)``.更简单的语法,没有多余的局部变量或foreach()中的长括号的屏幕污染. (46认同)
  • 这里的讨论给出了答案:http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID = 2386791&SiteID = 1基本上,我们决定保持扩展方法在功能上"纯粹".当使用扩展方法时,ForEach会鼓励副作用,这不是意图. (34认同)
  • "我讨厌看到以下内容" - 问题不是关于你的个人喜好,而是通过添加无关的换行和一对无关的大括号让你的代码变得更加可恨.不那么可恶的是`list.ForEach(item => item.DoSomething())`.谁说它是一个清单?明显的用例是在链的末尾; 使用foreach语句,你必须在`in`之后放置整个链,并且在块内执行操作,这远不那么自然或一致. (17认同)
  • 如果你有C背景,'foreach'可能看起来更清晰,但内部迭代更清楚地表明了你的意图.这意味着你可以做'sequence.AsParallel().ForEach(...)'之类的事情. (16认同)
  • 我不确定你对类型检查的看法是否正确.如果枚举是参数化的,则在编译时检查`foreach`的类型.它只缺少非泛型集合的编译时类型检查,假设的"ForEach"扩展方法也是如此. (7认同)
  • @Morgan,Count和Reverse有哪些副作用?据我所知,他们对原始的枚举没有任何作用.与OrderBy及其亲属相同.原来无论如何都没有被触及. (5认同)
  • 大声笑,我做了,但我决定因为它开始反对每一个咆哮,然后它仍然采取这个位置. (3认同)
  • 使用现有的语言功能作为参数是一种弱点,不是吗?那么我们永远不会超越C#1. (3认同)
  • 这不能回答**为什么**'IEnumerable <>`上没有`ForEach`方法. (3认同)
  • 老问题,但你的最后一个要点是不正确的.`ForEach`无法链接,因为它的返回类型是`void`.当然,可以使用自定义实现. (2认同)
  • 我认为能够做到这一点很棒:`items.Where(item =&gt; [在物品上测试])。ForEach([做东西])而不是`items.Where(item =&gt; [在物品上测试] ).ToList()。ForEach([do stuff])`。“ ToList”似乎没有添加任何内容,并且使其可读性稍差(在我看来,当您有多个链接时,嵌套的for循环绝对不可读)。 (2认同)
  • 我什至看不到最糟糕的答案。您的喜好不是原因 (2认同)
  • “我不想看到”但 `enumerable.ToList().ForEach(i =&gt; ... )` 更糟糕。 (2认同)

aku*_*aku 73

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

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

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

  • 扩展方法允许这样做.如果已存在给定方法名称的实例实现,则使用该方法而不是扩展方法.因此,您可以实现一个扩展方法ForEach,而不是它与List <>.ForEach方法冲突. (8认同)
  • 从扩展方法编程指南(http://msdn.microsoft.com/en-us/library/bb383977.aspx):永远不会调用与接口或类方法具有相同名称和签名的扩展方法.对我来说似乎很明显. (7认同)
  • 关于List <>的一个有趣的事情.ForEach和Array.ForEach是他们实际上没有在内部使用foreach构造.它们都使用plain for循环.也许这是表演的事情(http://diditwith.net/2006/10/05/PerformanceOfForeachVsListForEach.aspx).但鉴于此,Array和List都实现了ForEach方法,令人惊讶的是它们至少没有为IList <>实现扩展方法,如果不是IEnumerable <>也是如此. (5认同)
  • Cameron,相信我,我知道扩展方法是如何工作的:) List继承IEnumerable所以它将是非显而易见的事情. (3认同)
  • 我在说些不同的东西吗?我认为当许多类具有ForEach方法时它们并不好,但是其中一些可能以不同的方式工作. (3认同)

Jay*_*uzi 51

你可以编写这个扩展方法:

// 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

优点

允许链接:

MySequence
    .Apply(...)
    .Apply(...)
    .Apply(...);
Run Code Online (Sandbox Code Playgroud)

缺点

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

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

    return source;
}
Run Code Online (Sandbox Code Playgroud)

这可能与运输C#库有太大不同; 不熟悉您的扩展方法的读者将不知道您的代码是什么.

  • 难道你不能只使用Select而不是Apply方法吗?在我看来,对每个元素应用一个动作并产生它就是Select所做的.例如`numbers.Select(n => n*2);` (3认同)
  • 这相当于`Select(e =&gt; { action(e); return e; })` (3认同)
  • @rasmusvhansen 实际上,`Select()` 表示将函数应用于每个元素并返回该函数的值,其中 `Apply()` 表示将 `Action` 应用于每个元素并返回 __original__ 元素。 (2认同)

man*_*aus 33

这里的讨论给出了答案:

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

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

  • 这种推理是完全伪造的。*用例*是需要副作用的...如果没有ForEach,则必须编写一个foreach循环。ForEach的存在不会助长副作用,而缺少它不会减少副作用。 (6认同)
  • 另见http://blogs.msdn.com/b/ericlippert/archive/2009/05/18/foreach-vs-foreach.aspx.Ddoing违反了所有其他序列运算符所基于的函数式编程原则.显然,调用此方法的唯一目的是引起副作用 (5认同)
  • @MichaelFreidgeim 的存档现已失效链接:https://web.archive.org/web/20151205151706/http://blogs.msdn.com/b/ericlippert/archive/2009/05/18/foreach-vs-foreach。 ASPX (2认同)

Chr*_*ryk 17

虽然我同意foreach在大多数情况下使用内置构造更好,但我发现在ForEach <>扩展中使用这种变体比在常规中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;
}
Run Code Online (Sandbox Code Playgroud)
var people = new[] { "Moe", "Curly", "Larry" };
people.ForEach((i, p) => Console.WriteLine("Person #{0} is {1}", i, p));
Run Code Online (Sandbox Code Playgroud)

会给你:

Person #0 is Moe
Person #1 is Curly
Person #2 is Larry
Run Code Online (Sandbox Code Playgroud)


Jay*_*uzi 14

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

利弊

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

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

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

缺点

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

如果实现列表抛出异常,则永远不会对单个元素执行操作.

如果枚举是无限的(就像自然数字一样),那你运气不好.


Aar*_*ell 13

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

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

不错的小扩展方法.

  • col可以为null ...你也可以检查一下. (2认同)
  • 我发现每个人都检查参数是否为null时很有意思,当不检查结果是否也导致异常时... (2认同)

Sco*_*man 8

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

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

collection.Where(i => i.Name = "hello").Select(i => i.FullName);
Run Code Online (Sandbox Code Playgroud)

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

关于ForEach的具体论点是,基于对扩展方法的约束(即扩展方法永远不会覆盖具有相同签名的继承方法),可能存在这样一种情况:自定义扩展方法在所有可用的类上都可用IEnumerable <T>除了List <T>.当方法开始表现不同时,这可能会导致混淆,具体取决于是否调用了扩展方法或继承方法.


Mar*_*ijn 7

您可以使用(可链接但经过懒惰评估)Select,首先执行操作,然后返回标识(如果您愿意,还可以返回其他内容)

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

你需要确保它仍然被评估,无论Count()是最简单的操作(枚举afaik)还是你需要的其他操作.

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

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

然后上面的代码变得people.WithLazySideEffect(p => Console.WriteLine(p))有效地等同于foreach,但是懒惰和可链接.


Dav*_*sen 5

注意,MoreLINQ NuGet提供了ForEach您要查找的扩展方法(以及Pipe执行委托并产生其结果的方法)。看到: