436

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

为什么界面上没有ForEach扩展方法?IEnumerable还是在任何地方?唯一获得ForEach方法的类是List<>. 是否有它丢失的原因,也许是性能?

4

21 回答 21

214

语言中已经foreach包含一个语句,大部分时间都可以完成这项工作。

我不想看到以下内容:

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

代替:

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

后者在大多数情况下更清晰、更容易阅读,尽管打字时间可能会更长一些。

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

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

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

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

于 2008-09-19T12:00:44.577 回答
86

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

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

public static void ForEach<T>(
    this IEnumerable<T> source,
    Action<T> action)
{
    foreach (T element in source) 
        action(element);
}
于 2008-09-19T11:53:52.033 回答
57

您可以编写此扩展方法:

// 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# 库相差太大;不熟悉您的扩展方法的读者将不知道如何处理您的代码。

于 2008-10-19T17:00:08.747 回答
42

这里的讨论给出了答案:

实际上,我目睹的具体讨论确实取决于功能纯度。在表达式中,经常会假设没有副作用。拥有 ForEach 会特别引起副作用,而不仅仅是忍受它们。——基思·法默(合伙人)

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

于 2008-10-12T10:47:42.683 回答
22

虽然我同意在大多数情况下使用内置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;
}
例子
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
于 2008-09-19T14:55:52.593 回答
17

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

优点

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

句法噪音非常温和(仅添加了一些无关代码)。

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

缺点

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

如果意识到列表引发异常,您将永远无法对单个元素采取行动。

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

于 2008-10-19T16:51:12.510 回答
16

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

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

不错的小扩展方法。

于 2008-09-19T11:58:11.353 回答
8

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

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

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

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

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

于 2008-09-19T14:07:48.810 回答
7

您可以使用 (chainable, but lazily evaluation) 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,但是惰性和可链接。

于 2013-02-21T08:25:16.763 回答
6

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

于 2014-01-01T20:47:15.143 回答
4

@币币

foreach 扩展方法的真正强大之处在于Action<>无需在代码中添加不必要的方法即可重用。假设您有 10 个列表,并且您想对它们执行相同的逻辑,并且相应的函数不适合您的类并且没有被重用。而不是有十个 for 循环,或者一个显然是不属于的助手的通用函数,您可以将所有逻辑保存在一个地方(Action<>. 所以,几十行被替换为

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

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

ETC...

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

于 2008-11-01T03:48:04.943 回答
3

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

于 2008-09-19T11:52:34.563 回答
3

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

Seq.iter doSomething myIEnumerable

于 2008-09-19T12:06:11.067 回答
3

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

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

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

于 2018-10-14T06:46:59.797 回答
2

是我还是 List<T>.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);
}

(有关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-我的/

于 2008-09-19T13:26:13.760 回答
2

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

于 2008-09-19T14:12:08.877 回答
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

于 2009-02-01T21:02:11.483 回答
2

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

于 2009-11-11T17:04:54.820 回答
2

我想扩展Aku 的回答

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

private static IEnumerable<T> ForEach<T>(IEnumerable<T> xs, Action<T> f) {
    foreach (var x in xs) {
        f(x); yield return x;
    }
}
于 2016-02-17T12:11:38.900 回答
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;
        });
    }
}
于 2021-05-18T14:57:01.007 回答
-1

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

在代码中使用了这两种方法的地方进行了一些重构之后,我更喜欢 .ForEach,因为我必须寻找测试失败/运行时失败才能找到 foreach 问题。

于 2008-11-11T10:45:55.363 回答