foreach vs someList.ForEach(){}


167

コレクションを反復処理するには、明らかに多くの方法があります。違いがあるかどうか、またはなぜ他の方法を使用するのか興味があります。

最初のタイプ:

List<string> someList = <some way to init>
foreach(string s in someList) {
   <process the string>
}

他の方法:

List<string> someList = <some way to init>
someList.ForEach(delegate(string s) {
    <process the string>
});

私は頭の上で、上記で使用した匿名のデリゲートの代わりに、再利用可能なデリゲートを指定できると思います...


回答:


134

2つの間に1つの重要で便利な違いがあります。

.ForEachはforループを使用してコレクションを反復するため、これは有効です(編集:.net 4.5より前 -実装が変更され、両方ともスローします):

someList.ForEach(x => { if(x.RemoveMe) someList.Remove(x); }); 

一方foreach、列挙子を使用するため、これは無効です。

foreach(var item in someList)
  if(item.RemoveMe) someList.Remove(item);

tl; dr:このコードをアプリケーションにコピーアンドペーストしないでください。

これらの例には、ベストプラクティスではありません、彼らはちょうど間の違いを証明しているForEach()としますforeach

forループ内のリストから項目を削除すると、副作用が生じる可能性があります。最も一般的なものは、この質問へのコメントで説明されています。

一般に、リストから複数のアイテムを削除する場合は、削除するアイテムの決定を実際の削除から分離する必要があります。コードをコンパクトに保つ​​ことはできませんが、アイテムを見逃さないことが保証されます。


35
それでも、代わりにsomeList.RemoveAll(x => x.RemoveMe)を使用する必要があります
Cidadeをマークする

2
Linqを使用すると、すべてのことをよりよく行うことができます。foreach内でコレクションを変更する例を示したところです...

2
RemoveAll()はList <T>のメソッドです。
Mark Cidade、

3
あなたはおそらくこれに気づいているでしょうが、人々はこの方法でアイテムを削除することに注意すべきです。アイテムNを削除すると、イテレーションはアイテム(N + 1)をスキップし、デリゲートに表示されないか、独自のforループでこれを行った場合と同じように、アイテムを削除する機会がありません。
El Zorko

9
forループを使用してリストを逆方向に反復すると、インデックスシフトの問題なしにアイテムを削除できます。
S.TarıkÇetin16年

78

ここには(VS2005とC#2.0で)いくつかのコードがあり、以前のエンジニアは自分が書いたすべてのコードのlist.ForEach( delegate(item) { foo;});代わりに使用することはありませんでしたforeach(item in list) {foo; };。たとえば、dataReaderから行を読み取るためのコードのブロック。

彼らがなぜこれをしたのか私はまだ正確にはわかりません。

の欠点は次のlist.ForEach()とおりです。

  • C#2.0ではより冗長です。ただし、C#3以降では、 " =>"構文を使用して簡潔な表現を作成できます。

  • あまり馴染みがない。このコードを維持する必要がある人は、なぜあなたがそれをそのようにしたのか疑問に思うでしょう。ライターを賢く見せること以外に理由はないと判断するのにしばらく時間がかかりました(残りのコードの品質がそれを損なった)。また})、デリゲートコードブロックの末尾に" " があり、読みにくくなりました。

  • Bill Wagnerの本「効果的なC#:C#を改善するための50の特定の方法」も参照してください。forループやwhileループなどの他のループよりもforeachの方が好ましい理由について説明しています。ループ。コンパイラの将来のバージョンでより高速な手法を使用できるようになった場合は、コードを変更するのではなく、foreachと再構築を使用することで無料でこれを入手できます。

  • foreach(item in list)構文は、使用することができますbreakまたはcontinueあなたが反復やループを終了する必要がある場合。ただし、foreachループ内でリストを変更することはできません。

list.ForEach少し速いのには驚きました。しかし、それはおそらくそれを全体で使用する正当な理由ではありません。アプリケーションが、ループ制御ではなくデータベースまたはWebサービスを使用している場合、ほとんどの場合、時間が経過します。そして、それをforループに対してもベンチマークしましたか?list.ForEach内部的にそれを使用し、より高速に起因する可能性がありforラッパーのないループはさらに高速になります。

このlist.ForEach(delegate)バージョンが何らかの意味で「より機能的」であることに同意しません。関数に関数を渡しますが、結果やプログラム編成に大きな違いはありません。

私はforeach(item in list)「あなたがそれをどのようにしたいかを正確に言っている」とは思いません- for(int 1 = 0; i < count; i++)ループはそれを行い、foreachループは制御の選択をコンパイラに任せます。

私の気持ちは、新しいプロジェクトでforeach(item in list)は、一般的な使用法と読みやすさを守るためにほとんどのループで使用list.Foreach()し、C#3 " =>"演算子でよりエレガントにまたはコンパクトに何かを実行できる短いブロックにのみ使用することです。そのような場合は、よりも具体的なLINQ拡張メソッドがすでに存在している可能性がありForEach()ます。かどうかを確認しWhere()Select()Any()All()Max()または他の多くのLINQの方法の一つは、すでにあなたがループからやりたいしません。


ただ好奇心のために...マイクロソフトの実装を見... referencesource.microsoft.com/#mscorlib/system/collections/...
アレクサンドル

17

楽しみのために、リストをリフレクターにポップしました。これが結果のC#です。

public void ForEach(Action<T> action)
{
    if (action == null)
    {
        ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
    }
    for (int i = 0; i < this._size; i++)
    {
        action(this._items[i]);
    }
}

同様に、foreachで使用されるEnumeratorのMoveNextは次のとおりです。

public bool MoveNext()
{
    if (this.version != this.list._version)
    {
        ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
    }
    if (this.index < this.list._size)
    {
        this.current = this.list._items[this.index];
        this.index++;
        return true;
    }
    this.index = this.list._size + 1;
    this.current = default(T);
    return false;
}

List.ForEachは、MoveNextよりはるかに削減されています-はるかに少ない処理-JITが効率的なものになる可能性が高くなります。

さらに、foreach()は、何があっても新しいEnumeratorを割り当てます。GCはあなたの友達ですが、あなたが繰り返し同じforeachのをやっている場合は、同じデリゲートを再利用とは対照的に、これは、より多くの使い捨てのオブジェクトを行います- しかし、これは本当にフリンジケースであります- 。通常の使用法では、違いはほとんどないか、まったくありません。


1
foreachによって生成されたコードがコンパイラのバージョン間で同じである保証はありません。生成されたコードは、将来のバージョンで改善される可能性があります。
アンソニー

1
.NET Core 3.1以降、ForEachはさらに高速です。
ジャックモット

13

私はそれらを区別する2つのあいまいなことを知っています。行け!

まず、リストの各項目にデリゲートを作成するという古典的なバグがあります。foreachキーワードを使用すると、すべてのデリゲートがリストの最後の項目を参照することになります。

    // A list of actions to execute later
    List<Action> actions = new List<Action>();

    // Numbers 0 to 9
    List<int> numbers = Enumerable.Range(0, 10).ToList();

    // Store an action that prints each number (WRONG!)
    foreach (int number in numbers)
        actions.Add(() => Console.WriteLine(number));

    // Run the actions, we actually print 10 copies of "9"
    foreach (Action action in actions)
        action();

    // So try again
    actions.Clear();

    // Store an action that prints each number (RIGHT!)
    numbers.ForEach(number =>
        actions.Add(() => Console.WriteLine(number)));

    // Run the actions
    foreach (Action action in actions)
        action();

List.ForEachメソッドにはこの問題はありません。反復の現在の項目は、値として引数として外部ラムダに渡され、内部ラムダはその引数を独自のクロージャーで正しくキャプチャします。問題が解決しました。

(残念ながら、ForEachは拡張メソッドではなくListのメンバーであると思いますが、自分で定義するのは簡単なので、列挙可能な型でこの機能を使用できます。)

第二に、ForEachメソッドのアプローチには制限があります。イールドリターンを使用してIEnumerableを実装している場合、ラムダ内でイールドリターンを行うことはできません。したがって、このメソッドでは、戻り値を生成するためにコレクション内の項目をループすることはできません。foreachキーワードを使用して、ループ内の現在のループ値のコピーを手動で作成することにより、クロージャの問題を回避する必要があります。

詳細はこちら


5
あなたが言及する問題foreachは、C#5で「修正」されています。stackoverflow.com
questions /

13

someList.ForEach()呼び出しは簡単に並列化できるのではないかと思いますが、通常foreachは並列実行はそれほど簡単ではありません。異なるコアでいくつかの異なるデリゲートを簡単に実行できますが、これは通常のでは簡単ではありませんforeach
ちょうど私の2セント


2
彼は、ランタイムエンジンが自動的に並列化できることを意味していると思います。それ以外の場合、foreachと.ForEachの両方は、各アクションデリゲートでプールからのスレッドを使用して手動で並列化できます
Isak Savo

@Isakですが、これは誤った仮定です。匿名メソッドがローカルまたはメンバーを巻き上げた場合、ランタイムは容易に並列化できません
Rune FS

6

彼らが言うように、悪魔は細部に...

コレクションを列挙する2つの方法の最大の違いは、foreach状態を保持するのに対し、状態を保持ForEach(x => { })しないことです。

しかし、もう少し深く掘り下げてみましょう。これは、決定に影響を与える可能性があることを知っておくべきことがいくつかあり、どちらの場合もコーディングするときに注意すべきいくつかの注意事項があるためです。

使用することができますList<T>動作を観察するために私たちの小さな実験で。この実験では、.NET 4.7.2を使用しています。

var names = new List<string>
{
    "Henry",
    "Shirley",
    "Ann",
    "Peter",
    "Nancy"
};

foreach最初にこれを繰り返しましょう:

foreach (var name in names)
{
    Console.WriteLine(name);
}

これを次のように拡張できます。

using (var enumerator = names.GetEnumerator())
{

}

列挙子を手に入れ、カバーの下を見ると次のようになります。

public List<T>.Enumerator GetEnumerator()
{
  return new List<T>.Enumerator(this);
}
    internal Enumerator(List<T> list)
{
  this.list = list;
  this.index = 0;
  this.version = list._version;
  this.current = default (T);
}

public bool MoveNext()
{
  List<T> list = this.list;
  if (this.version != list._version || (uint) this.index >= (uint) list._size)
    return this.MoveNextRare();
  this.current = list._items[this.index];
  ++this.index;
  return true;
}

object IEnumerator.Current
{
  {
    if (this.index == 0 || this.index == this.list._size + 1)
      ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumOpCantHappen);
    return (object) this.Current;
  }
}

2つのことがすぐに明らかになります。

  1. 元になるコレクションの詳細な知識を持つステートフルオブジェクトが返されます。
  2. コレクションのコピーは浅いコピーです。

もちろん、これは決してスレッドセーフではありません。上記で指摘したように、反復しながらコレクションを変更することは、単に悪いモジョです。

しかし、反復の間にコレクションをいじくる外部の手段によって、反復の間にコレクションが無効になるという問題はどうですか?ベストプラクティスでは、操作中および反復中にコレクションのバージョン管理を行い、バージョンをチェックして、基になるコレクションが変更されたことを検出することをお勧めします。

ここで、事態は非常に曖昧になります。Microsoftのドキュメントによると:

要素の追加、変更、削除など、コレクションに変更が加えられた場合、列挙子の動作は未定義です。

さて、それはどういう意味ですか?例として、List<T>例外処理を実装しているからといって、実装しているすべてのコレクションがIList<T>同じように動作するわけではありません。これは、リスコフの代替原則に明らかに違反しているようです。

スーパークラスのオブジェクトは、アプリケーションを壊すことなく、そのサブクラスのオブジェクトと置き換えることができます。

もう1つの問題は、列挙子を実装する必要があることです。つまり、IDisposable呼び出し元が間違っている場合だけでなく、作成者がDisposeパターンを正しく実装していない場合でも、メモリリークが発生する可能性があります。

最後に、寿命の問題があります...イテレータが有効であるが、基になるコレクションがなくなった場合はどうなりますか?現在のスナップショットだったものが、あなたが収集し、そのイテレータの寿命を分離する場合...、あなたはトラブルを求めています。

調べてみましょうForEach(x => { })

names.ForEach(name =>
{

});

これは次のように拡張されます。

public void ForEach(Action<T> action)
{
  if (action == null)
    ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
  int version = this._version;
  for (int index = 0; index < this._size && (version == this._version || !BinaryCompatibility.TargetsAtLeast_Desktop_V4_5); ++index)
    action(this._items[index]);
  if (version == this._version || !BinaryCompatibility.TargetsAtLeast_Desktop_V4_5)
    return;
  ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
}

重要な注意点は次のとおりです。

for (int index = 0; index < this._size && ... ; ++index) action(this._items[index]);

このコードは列挙子を割り当てず(には何もないDispose)、反復中に一時停止しません。

これは、基になるコレクションの浅いコピーも実行しますが、コレクションは今のところスナップショットです。作成者がコレクションの変更をチェックしたり、「古くなったり」するチェックを正しく実装していなくても、スナップショットは引き続き有効です。

これは決して生涯問題の問題からあなたを保護しません...根底にあるコレクションが消えた場合、今は何があったかを指し示す浅いコピーを持っています...しかし、少なくともあなたはDispose問題を抱えていません孤立したイテレータを扱います...

はい、私はイテレータを言いました...時々状態を持つことは有利です。データベースカーソルのようなものを維持したいとします。たぶん、複数のforeachスタイルを使用Iterator<T>する方法です。寿命の問題が多すぎるため、このスタイルのデザインは個人的に嫌いです。あなたは、自分が信頼しているコレクションの作者の優れた恵みに頼っています(自分ですべてを最初から自分で書く場合を除きます)。

常に3番目のオプションがあります...

for (var i = 0; i < names.Count; i++)
{
    Console.WriteLine(names[i]);
}

セクシーではありませんが、歯が生えています(トムクルーズと映画「ザファーム」に謝罪)

それはあなたの選択ですが、今あなたは知っており、それは知識のあるものになることができます。


5

匿名のデリゲートに名前を付けることができます:-)

そして、あなたは2番目を次のように書くことができます:

someList.ForEach(s => s.ToUpper())

私はどちらを好み、多くのタイピングを節約します。

ヨアヒムが言うように、並列処理は2番目の形式に適用する方が簡単です。


2

舞台裏では、匿名のデリゲートが実際のメソッドに変換されるため、コンパイラーが関数をインライン化することを選択しなかった場合、2番目の選択肢でオーバーヘッドが発生する可能性があります。さらに、匿名デリゲートの例の本文で参照されるローカル変数は、新しいメソッドにコンパイルされるという事実を隠すためのコンパイラトリックのために、性質が変化します。C#がこの魔法をどのように実行するかについての詳細はこちら:

http://blogs.msdn.com/oldnewthing/archive/2006/08/04/688527.aspx


2

ForEach関数は、ジェネリッククラスListのメンバーです。

内部コードを再現するために、次の拡張機能を作成しました。

public static class MyExtension<T>
    {
        public static void MyForEach(this IEnumerable<T> collection, Action<T> action)
        {
            foreach (T item in collection)
                action.Invoke(item);
        }
    }

つまり、通常のforeach(または必要に応じてループ)を使用しています。

一方、デリゲート関数の使用は、関数を定義するもう1つの方法である次のコードです。

delegate(string s) {
    <process the string>
}

以下と同等です。

private static void myFunction(string s, <other variables...>)
{
   <process the string>
}

またはラブダ式を使用する:

(s) => <process the string>

2

ForEachスコープ全体(デリゲート関数)は1行のコード(関数の呼び出し)として扱われ、ブレークポイントを設定したり、コードにステップインしたりすることはできません。未処理の例外が発生すると、ブロック全体がマークされます。


2

List.ForEach()はより機能的であると考えられています。

List.ForEach()あなたがしたいことを言います。 foreach(item in list)また、あなたがそれをどのようにしたいかを正確に述べています。これにより、将来のList.ForEach実装方法を自由に変更できます。たとえば、List.ForEach現時点で架空の.Netのバージョンが常に並行して実行される可能性があります。この時点では、誰もが通常はアイドル状態にある多数のCPUコアを持っているという前提です。

一方、foreach (item in list)ループをもう少し制御できます。たとえば、アイテムがなんらかの順序で繰り返されることがわかっているため、アイテムが何らかの条件を満たしている場合、途中で簡単に中断できます。


この問題についてのいくつかの最近のコメントはここにあります:

https://stackoverflow.com/a/529197/3043


1

2番目の方法では、拡張メソッドを使用して、リスト内の各要素に対してデリゲートメソッドを実行します。

このように、別のデリゲート(=メソッド)呼び出しがあります。

さらに、forループでリストを反復する可能性があります。


1

注意すべきことの1つは、Generic .ForEachメソッドを終了する方法です。この説明を参照してください。リンクはこの方法が最速であると言っているようですが。理由がわからない-コンパイルすると同等になると思う...


0

方法があります、私は自分のアプリでそれをしました:

List<string> someList = <some way to init>
someList.ForEach(s => <your actions>);

foreachからのアイテムとして使用できます

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.