Why there is no ForEach extension method on IEnume

2018-12-31 18:07发布

Inspired by another question asking about the missing Zip function:

Why is there no ForEach extension method in the Enumerable class? Or anywhere? The only class that gets a ForEach method is List<>. Is there a reason why it's missing (performance)?

21条回答
呛了眼睛熬了心
2楼-- · 2018-12-31 18:13

There is already a foreach statement included in the language that does the job most of the time.

I'd hate to see the following:

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

Instead of:

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

The latter is clearer and easier to read in most situation, although maybe a bit longer to type.

However, I must admit I changed my stance on that issue; a ForEach() extension method would indeed be useful in some situations.

Here are the major differences between the statement and the method:

  • Type checking: foreach is done at runtime, ForEach() is at compile time (Big Plus!)
  • The syntax to call a delegate is indeed much simpler: objects.ForEach(DoSomething);
  • ForEach() could be chained: although evilness/usefulness of such a feature is open to discussion.

Those are all great points made by many people here and I can see why people are missing the function. I wouldn't mind Microsoft adding a standard ForEach method in the next framework iteration.

查看更多
低头抚发
3楼-- · 2018-12-31 18:13

You could write this extension method:

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

Pros

Allows chaining:

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

Cons

It won't actually do anything until you do something to force iteration. For that reason, it shouldn't be called .ForEach(). You could write .ToList() at the end, or you could write this extension method, too:

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

    return source;
}

This may be too significant a departure from the shipping C# libraries; readers who are not familiar with your extension methods won't know what to make of your code.

查看更多
素衣白纱
4楼-- · 2018-12-31 18:14

You could use the (chainable, but lazily evaluated) Select, first doing your operation, and then returning identity (or something else if you prefer)

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

You will need to make sure it is still evaluated, either with Count() (the cheapest operation to enumerate afaik) or another operation you needed anyway.

I would love to see it brought in to the standard library though:

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

The above code then becomes people.WithLazySideEffect(p => Console.WriteLine(p)) which is effectively equivalent to foreach, but lazy and chainable.

查看更多
余生请多指教
5楼-- · 2018-12-31 18:15
长期被迫恋爱
6楼-- · 2018-12-31 18:16

So there has been a lot of comments about the fact that a ForEach extension method isn't appropriate because it doesn't return a value like the LINQ extension methods. While this is a factual statement, it isn't entirely true.

The LINQ extension methods do all return a value so they can be chained together:

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

However, just because LINQ is implemented using extension methods does not mean that extension methods must be used in the same way and return a value. Writing an extension method to expose common functionality that does not return a value is a perfectly valid use.

The specific arguement about ForEach is that, based on the constraints on extension methods (namely that an extension method will never override an inherited method with the same signature), there may be a situation where the custom extension method is available on all classes that impelement IEnumerable<T> except List<T>. This can cause confusion when the methods start to behave differently depending on whether or not the extension method or the inherit method is being called.

查看更多
孤独寂梦人
7楼-- · 2018-12-31 18:18

If you have F# (which will be in the next version of .NET), you can use

Seq.iter doSomething myIEnumerable

查看更多
登录 后发表回答