web-dev-qa-db-ja.com

IEnumerableにForEach拡張メソッドがないのはなぜですか?

欠落しているZip関数について尋ねる別の質問に触発されました:

ForEachクラスにEnumerable拡張メソッドがないのはなぜですか?それともどこか? ForEachメソッドを取得する唯一のクラスはList<>です。欠落している(パフォーマンス)理由はありますか?

341

ほとんどの場合、仕事をする言語に含まれるforeachステートメントが既にあります。

私は以下を見たくない:

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

の代わりに:

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

後者はより明確で読みやすいほとんどの場合ですが、入力するのに少し時間がかかるかもしれません。

ただし、その問題に対する姿勢を変えたことを認めなければなりません。 ForEach()拡張メソッドは、実際にはいくつかの状況で役立ちます。

ステートメントとメソッドの主な違いは次のとおりです。

  • 型チェック:foreachは実行時に行われ、ForEach()はコンパイル時に行われます(Big Plus!)
  • デリゲートを呼び出す構文は、実際にははるかに単純です:objects.ForEach(DoSomething);
  • ForEach()は連鎖させることができます。ただし、このような機能の悪/有用性については議論の余地があります。

これらはすべてここにいる多くの人々によって作られた素晴らしいポイントであり、人々がこの機能を欠いている理由がわかります。次のフレームワークの繰り返しで、Microsoftが標準のForEachメソッドを追加してもかまいません。

181
Coincoin

ForEachメソッドはLINQの前に追加されました。 ForEach拡張を追加すると、拡張メソッドの制約のため、Listインスタンスに対して呼び出されることはありません。追加されなかった理由は、既存のものと干渉しないためだと思います。

ただし、この小さな素敵な機能が本当に見当たらない場合は、独自のバージョンを展開できます

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

この拡張メソッドを書くことができます:

// 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#ライブラリからの逸脱が大きすぎる場合があります。拡張メソッドに慣れていない読者は、コードをどのように作成すればよいかわかりません。

48
Jay Bazuzi

ここの議論 は答えを与えます:

実際、私が目撃した特定の議論は、実際には機能的な純度にかかっています。式では、副作用がないという仮定が頻繁に行われます。 ForEachを使用することは、副作用を単に我慢するのではなく、特に副作用を招くことです。 -キースファーマー(パートナー)

基本的に、拡張メソッドを機能的に「純粋」に保つことを決定しました。 ForEachは、Enumerable拡張メソッドを使用するときに副作用を助長しますが、これは意図ではありませんでした。

32
mancaus

ほとんどの場合、組み込みの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
16
Chris Zwiryk

回避策の1つは、.ToList().ForEach(x => ...)を記述することです。

長所

わかりやすい-読者はC#に同梱されているものを知るだけでよく、追加の拡張メソッドは必要ありません。

構文上のノイズは非常に穏やかです(わずかなコードを追加するだけです)。

通常、ネイティブの.ForEach()はコレクション全体を認識しなければならないため、余分なメモリは必要ありません。

cons

操作の順序は理想的ではありません。むしろ、1つの要素を実現し、それに基づいて行動し、繰り返します。このコードは、最初にすべての要素を実現し、次にそれらの要素に順番に作用します。

リストが例外をスローすることに気付いた場合、単一の要素を操作することはできません。

列挙が(自然数のように)無限である場合、運が悪い。

14
Jay Bazuzi

私はいつも自分自身が、これをいつも私と一緒に持っているのだと思っていました:

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

素敵な小さな拡張メソッド。

12
Aaron Powell

そのため、ForEach拡張メソッドはLINQ拡張メソッドのような値を返さないため、適切ではないという事実について多くのコメントが寄せられています。これは事実に関する記述ですが、完全に真実ではありません。

LINQ拡張メソッドはすべて値を返すので、一緒にチェーンできます。

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

ただし、LINQが拡張メソッドを使用して実装されているからといって、拡張メソッドmustが同じ方法で使用され、値を返すという意味ではありません。値を返さない一般的な機能を公開する拡張メソッドを作成することは、完全に有効な使用法です。

ForEachについての具体的な主張は、拡張メソッドの制約に基づいて(つまり、拡張メソッドはnever継承されたメソッドを同じ署名でオーバーライドする)、 List<T>を除くIEnumerable<T>を含むすべてのクラスでカスタム拡張メソッドが利用できる状況。これにより、拡張メソッドまたは継承メソッドが呼び出されているかどうかによってメソッドの動作が異なる場合に混乱が生じる可能性があります。

7
Scott Dorman

(チェーン可能ですが、遅延評価された)Selectを使用して、最初に操作を実行し、次にID(または必要に応じて何か)を返すことができます

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と同等ですが、怠andでチェーン可能です。

6
Martijn

@Coincoin

Foreach拡張メソッドの真の力は、コードに不要なメソッドを追加せずにAction<>を再利用できることです。 10個のリストがあり、それらに対して同じロジックを実行したい場合、対応する関数はクラスに適合せず、再利用されません。 10個のforループ、または明らかに属していないヘルパーである汎用関数の代わりに、すべてのロジックを1か所に保持できます(Action<>。したがって、数十行が

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

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

等...

ロジックは1つの場所にあり、クラスを汚染していません。

4
diehard2

MoreLINQNuGetには、探しているForEach拡張メソッド(およびデリゲートを実行してその結果を生成するPipeメソッド)が用意されています。見る:

4
Dave Clausen

私はそれについてのブログ記事を書きました: http://blogs.msdn.com/kirillosenkov/archive/2009/01/31/foreach.aspx

.NET 4.0でこのメソッドを見たい場合は、ここで投票できます。 http://connect.Microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=27909

2
Kirill Osenkov

Akuの答え に拡張したいと思います。

最初に列挙可能オブジェクト全体を反復せずに副作用のみを目的としてメソッドを呼び出す場合は、これを使用できます。

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

3.5では、IEnumerableに追加されたすべての拡張メソッドがLINQサポート用にあります(System.Linq.Enumerableクラスで定義されていることに注意してください)。この投稿では、foreachがLINQに属さない理由を説明します。 Parallel.For?に似た既存のLINQ拡張メソッド

2
Neil

それは私ですか、それともList <T>です。ForeachはLinqによってほとんど廃止されました。もともとあった

foreach(X x in Y) 

yは単にIEnumerable(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 Genericsが登場し、リストになりました。 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 /

2
user18784

ほとんどのLINQ拡張メソッドは結果を返します。 ForEachは何も返さないため、このパターンには適合しません。

2
leppie

部分的には、言語設計者が哲学的な観点からこれに反対しているからです。

  • 機能を持たない(そしてテストする...)ことは、機能を持たせるよりも作業が少なくなります。
  • それは実際には短くありません(それがそうであるいくつかの通過関数のケースがありますが、それは主な使用ではないでしょう)。
  • Linqの目的ではない副作用をもたらすことが目的です。
  • すでに持っている機能と同じことをする別の方法があるのはなぜですか? (foreachキーワード)

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

2
McKay

F#(次のバージョンの.NETに含まれる予定)がある場合は、次を使用できます。

Seq.iter doSomething myIEnumerable

2
TraumaPony

何かを返したいときにselectを使用できます。そうでない場合は、おそらくコレクション内の何も変更したくないため、ToListを最初に使用できます。

2
Paco

Foreachを使用するには、リストをプライマリメモリに読み込む必要があります。ただし、IEnumerableの遅延読み込みの性質により、IEnumerableでForEachを提供しません。

ただし、(ToList()を使用して)積極的にロードすることにより、リストをメモリにロードし、ForEachを利用できます。

1
Arjun Bhalodiya

ForEach <T>の結果、foreachキーワードが実行時チェックされるコンパイル時型チェックが発生することを誰もまだ指摘していません。

コードで両方のメソッドが使用されている場所でリファクタリングを行ったので、foreachの問題を見つけるためにテストエラー/ランタイムエラーを追跡する必要があったため、.ForEachを優先します。

0
Squirrel