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


389

不足しているZip機能について尋ねる別の質問に触発されました:

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


おそらく以前の投稿に示唆されているように、foreachはC#とVB.NET(および他の多くの)の言語キーワードとしてサポートされています。
chrisb 2008

2
「公式の答え」へのリンクをここに関連する質問stackoverflow.com/questions/858978/...
Benjol


非常に重要な点の1つは、foreachループを見つけやすいことです。.ForEach(...)を使用すると、コードを見たときにこれを見逃しがちです。これは、パフォーマンスの問題がある場合に重要になります。もちろん、.ToList()と.ToArray()には同じ問題がありますが、使用方法は少し異なります。もう1つの理由は、.ForEachではソースリストを変更する(要素を削除/追加する)ことが一般的であるため、「純粋な」クエリではなくなる可能性があるためです。
DanielBişar2014年

並列化されたコードをデバッグするとき、私はしばしば後者が存在しないことを再発見するParallel.ForEachためEnumerable.ForEachだけにスワップを試み ます。C#は、ここで物事を簡単にするためのトリックを逃しました。
パニック大佐、2016年

回答:


198

ほとんどの場合、この作業を行う言語には、foreachステートメントが既に含まれています。

私は以下を見ることを嫌います:

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

の代わりに:

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

後者は、ほとんどの状況でより明確で読みやすくなりますがタイプするのが少し長くなる可能性があります。

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

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

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

これらはすべて、多くの人々によってここで作成された素晴らしい点であり、人々が機能を欠いている理由を私は見ることができます。Microsoftが次のフレームワークの反復で標準のForEachメソッドを追加してもかまいません。


39
ここでの議論は答えを与えます:forums.microsoft.com/MSDN/…基本的に、拡張メソッドを機能的に「純粋」に保つことを決定しました。ForEachは、拡張メソッドの使用時に副作用を助長しますが、これは意図されていませんでした。
mancaus 2008年

17
Cのバックグラウンドがある場合、 'foreach'はより明確に見えるかもしれませんが、内部反復により意図がより明確に示されます。つまり、「sequence.AsParallel()。ForEach(...)」のようなことを行うことができます。
ジェイバズジ09

10
型チェックに関するあなたの主張が正しいかどうかはわかりません。foreachenumerableがパラメーター化されている場合、コンパイル時に型チェックされます。架空のForEach拡張メソッドとは異なり、非ジェネリックコレクションのコンパイル時の型チェックだけが欠けています。
リチャードプール2011年

49
拡張メソッドは、次のように、ドット表記を使用したチェーンLINQで非常に役立ちますcol.Where(...).OrderBy(...).ForEach(...)。構文がはるかに単純になり、冗長なローカル変数やforeach()の長いブラケットによる画面の汚染がなくなりました。
JacekGorgoń12年

22
「私は次のことを見るのが嫌いだ」-質問はあなたの個人的な好みに関するものではなく、無関係な改行と余分な中括弧のペアを追加することでコードをより嫌なものにしました。それほど嫌いではないですlist.ForEach(item => item.DoSomething())。そして、それはリストだと誰が言うのですか?明らかなユースケースはチェーンの最後です。foreachステートメントでは、チェーン全体をの後ろに配置する必要がありin、ブロック内で実行される操作ははるかに自然または一貫性が低くなります。
ジムバルター2017

73

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

ただし、この小さな便利な機能を見逃した場合は、独自のバージョンをロールアウトできます

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

11
拡張メソッドはこれを可能にします。特定のメソッド名のインスタンス実装がすでに存在する場合は、拡張メソッドの代わりにそのメソッドが使用されます。したがって、拡張メソッドForEachを実装して、List <>。ForEachメソッドと衝突しないようにすることができます。
キャメロンマクファーランド

3
キャメロン、私は拡張メソッドがどのように機能するか知っていると信じています:)リストはIEnumerableを継承しているため、自明ではありません
aku

9
拡張メソッドプログラミングガイド(msdn.microsoft.com/en-us/library/bb383977.aspx)から:インターフェイスまたはクラスメソッドと同じ名前と署名の拡張メソッドが呼び出されることはありません。私にはかなり自明のようです。
キャメロンマクファーランド

3
私は何か違うことを話しているのですか?多くのクラスがForEachメソッドを持っているのは良くないと思いますが、一部は異なる動作をする場合があります。
aku

5
List <>。ForEachとArray.ForEachについて注目すべき1つの興味深い点は、実際には内部でforeachコンストラクトを使用していないことです。どちらも単純なforループを使用しています。多分それはパフォーマンスのことです(diditwith.net/2006/10/05/PerformanceOfForeachVsListForEach.aspx)。ただし、ArrayとListの両方がForEachメソッドを実装していることを考えると、IEnumerable <>の拡張メソッドではない場合でも、少なくともIList <>の拡張メソッドを実装していないことは驚くべきことです。
Matt Dotson、2012年

53

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

// 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#ライブラリからの逸脱が大きすぎる可能性があります。あなたの拡張メソッドに慣れていない読者は、あなたのコードをどうするかを知らないでしょう。


1
以下のように書かれている場合、あなたの最初の関数は「リアライズ」メソッドから/ wを使用することができます{foreach (var e in source) {action(e);} return source;}
jjnguy

3
Applyメソッドの代わりにSelectを使用できませんでしたか?各要素にアクションを適用してそれを生成することが、まさにSelectが行うことであるように私には思えます。例numbers.Select(n => n*2);
rasmusvhansen 2013

1
この目的でSelectを使用するよりもApplyの方が読みやすいと思います。Selectステートメントを読むとき、私は「内部から何かを取得する」と読みます。
ロブラング博士

2
実際@rasmusvhansen、Select()各要素に関数を適用して関数の値を返す言うApply()適用言うAction各要素および戻り元の要素。
NetMage

1
これは同等ですSelect(e => { action(e); return e; })
ジム・Balter

35

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

実際、私が目撃した具体的な議論は、実際には機能の純粋さにかかっていました。式の中には、副作用がないことを前提とすることがよくあります。ForEachを使用すると、副作用を我慢するだけでなく、特に副作用が発生します。-キース・ファーマー(パートナー)

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


6
blogs.msdn.com/b/ericlippert/archive/2009/05/18/…も参照してください。Ddoingは、他のすべてのシーケンス演算子が基づいている関数型プログラミングの原則に違反しています。明らかに、このメソッドの呼び出しの唯一の目的は、副作用を引き起こすことです
Michael Freidgeim '07 / 07/23

7
その推論は完全に偽です。ユースケースは、副作用が必要であること...のForEachせずに、あなたはforeachループを記述する必要があります。ForEachの存在は副作用を助長せず、その不在は副作用を減らしません。
ジムBalter

拡張メソッドを書くのがどれほど簡単であるかを考えると、それが言語標準ではないという理由は文字通りありません。
キャプテンプリニー

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

素晴らしい拡張ですが、ドキュメントを追加できますか?戻り値の使用目的は何ですか?特にintではなくindex varなのはなぜですか?サンプルの使用法?
マークT

マーク:戻り値はリスト内の要素の数です。暗黙の型付けのおかげで、インデックス特にintとして型付けされます。
Chris Zwiryk 2009年

@Chris、上記のコードに基づくと、戻り値はリスト内の要素の数ではなく、リスト内の最後の値のゼロベースのインデックスです。
Eric Smith、

@Chris、そうではありません。値が取得された後にインデックスがインクリメントされるため(後置インクリメント)、最初の要素が処理された後、インデックスは1などになります。したがって、戻り値は実際には要素数です。
MartinStettner、2009

1
@MartinStettner 5/16のEric Smithからのコメントは、以前のバージョンからのものでした。彼は正しい値を返すように5/18にコードを修正しました。
Michael Blackburn

16

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

長所

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

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

.ForEach()とにかくネイティブはコレクション全体を実現する必要があるため、通常は追加のメモリは必要ありません。

短所

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

リストを実現すると例外がスローされる場合、単一の要素を操作することはできません。

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


1
a)これは、質問に対する答えでさえありません。b)Enumerable.ForEach<T>方法はないが方法はあると前もって述べていれば、この応答はより明確になりますList<T>.ForEach。c)「とにかく、ネイティブの.ForEach()がコレクション全体を実現する必要があるため、通常、追加のメモリは必要ありません。」-完全でまったくナンセンス。ここではそれがなかった場合はC#を提供することをEnumerable.ForEach <T>の実装は次のとおりです。public static void ForEach<T>(this IEnumerable<T> list, Action<T> action) { foreach (T item in list) action(item); }
ジムBalter

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

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


2
colはnullになる可能性があります...これも確認できます。
エイミーB

ええ、私は自分のライブラリをチェックインします。そこですぐにそれを実行したときに、それを逃してしまいました。
アーロンパウエル

誰もがアクションパラメータのnullをチェックしますが、source / colパラメータはチェックしないのは興味深いことです。
Cameron MacFarland、2010年

2
結果をチェックしないと例外も発生するので、皆がnullのパラメータをチェックするのは興味深いことです。
oɔɯǝɹJun

どういたしまして。Null Ref例外は、障害が発生したパラメーターの名前を通知しません。また、ループをチェックインして、同じ理由でcol [index#]がnullであると特定のNull Refをスローできるようにすることもできます
ロジャーウィルコック

8

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

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

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

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

ForEachについての具体的な議論は、拡張メソッドの制約に基づいて(つまり、拡張メソッドが同じシグネチャで継承されたメソッドをオーバーライドすること決してない)、カスタム要素拡張メソッドがimpelementであるすべてのクラスで使用できる場合があるということです。 IEnumerable >リストを除く>。これにより、拡張メソッドまたは継承メソッドが呼び出されているかどうかによって、メソッドの動作が異なる場合に混乱が生じる可能性があります。<T<T


7

(連鎖可能ですが、遅延評価されます)を使用して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と実質的に同等になりますが、レイジーでチェーン可能になります。


素晴らしい、返された選択がこのように機能することは決してありませんでした。式の構文ではこれを行うことができないと思うので、メソッドの構文をうまく使用できます(したがって、以前はこのように考えていませんでした)。
Alex KeySmith、2014

@AlexKeySmith C#にフォアベアのようなシーケンス演算子がある場合、式の構文でそれを行うことができます。ただし、ForEachの要点は、void型でアクションを実行することであり、C#の式でvoid型を使用することはできません。
ジムBalter

イムホ、今Selectはもうやるべきことをやらない。これは間違いなくOPが求めるものに対する解決策ですが、トピックの読みやすさに関しては、selectが関数を実行することを誰も期待していません。
Matthias Burger

@MatthiasBurger場合には、Selectそれはの実装に問題があります行うことになっています何をしないSelectではないコードと、その通話Select何に対して何ら影響ありません、Selectありませんが。
Martijn


4

@Coincoin

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

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

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

等...

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


foreach(var p in List1.Concat(List2).Concat(List3)){... do stuff ...}
Cameron MacFarland

のように単純な場合f(p){ }、省略形のを省略しますfor (var p in List1.Concat(List2).Concat(List2)) f(p);
スポールソン2010年

3

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


2
ForEachはAction <T>を使用します。これは、デリゲートの別の形式です
Aaron Powell

2
leppieの権利を除いて、すべてのEnumerable拡張メソッドは値を返すため、ForEachはパターンに適合しません。デリゲートはこれには入りません。
キャメロンマクファーランド

拡張メソッドが結果を返す必要がないということだけで、それは頻繁に使用される操作を呼び出す方法を追加する目的に役立ちます
Aaron Powell

1
確かに、Slace。それで値を返さない場合はどうでしょうか?
TraumaPony 2008

1
@Martijn iepublic IEnumerable<T> Foreach<T>(this IEnumerable<T> items, Action<T> action) { /* error check */ foreach (var item in items) { action(item); yield return item; } }
マッケイ

3

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

Seq.iter doSomething myIEnumerable


9
そのため、MicrosoftはC#およびVBのIEnumerableにForEachメソッドを提供しないことを選択しました。これは、純粋に機能的ではないためです。それでも、彼らはDID(name iter)をより機能的な言語F#で提供しました。彼らの論理は私をほのめかしている。
スコット

3

部分的には、言語デザイナーが哲学的観点からそれに同意しないためです。

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

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


2

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


a)これは質問に対する回答ではありません。b)ToListには余分な時間とメモリが必要です。
ジムバルター2017

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


これは実装されたことがありますか?私はそうは思いませんが、そのチケットを置き換える他のURLはありますか?MS接続は廃止されました
knocte

これは実装されていません。github.com/dotnet/runtime/issuesで
Kirill Osenkov


2

それは私ですか、それとも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でジェネリックスとリストが登場しました。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-を試してください。 俺の/


1
「foreach」用に生成されたコードには、try-finallyブロックが必要です。TのIEnumeratorがインターフェイスIDisposableを継承しているためです。したがって、生成されたfinallyブロックでは、TインスタンスのIEnumeratorがDisposedになります。
モーガンチェン

2

阿久の答えを詳しく説明したいと思います

列挙型全体を最初に反復せずに、副作用のためだけにメソッドを呼び出したい場合は、次のように使用できます。

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

1
これは同じであるSelect(x => { f(x); return x;})
ジムBalter

0

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

コードで両方の方法が使用されているリファクタリングをいくつか行った後、私は.ForEachを支持します。これは、foreachの問題を見つけるためにテストの失敗/ランタイムの失敗を突き止める必要があったためです。


5
これは本当ですか?foreachコンパイル時間のチェックが少なくなっていることがわかりません。もちろん、コレクションが非ジェネリックIEnumerableの場合、ループ変数はにobjectなりますが、仮想のForEach拡張メソッドについても同じことが言えます。
リチャードプール2011年

1
これは受け入れられた回答で言及されています。しかし、それは単に間違っています(そして上記のRichard Pooleは正しいです)。
ジムバルター2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.