196

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

最初のタイプ:

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

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

4

13 に答える 13

150

この 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副作用が生じる可能性があります。最も一般的なものは、この質問へのコメントで説明されています。

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

于 2008-10-22T14:50:46.683 に答える
85

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

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

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

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

  • あまりなじみがありません。このコードを保守しなければならない人は、なぜそのようにしたのか不思議に思うでしょう。理由はないと判断するのにしばらく時間がかかりましたが、おそらくライターを賢く見せるためでした (残りのコードの品質がそれを台無しにしていました)。また})、デリゲート コード ブロックの最後に " " があるため、読みづらくもありました。

  • Bill Wagner の著書「Effective C#: 50 Specific Ways to Improvement Your C#」も参照してください。ここでは、 foreach が for ループや while ループなどの他のループよりも好まれる理由について説明しています。ループ。コンパイラの将来のバージョンがより高速な手法を使用できるようになった場合、コードを変更するのではなく、foreach と再構築を使用することで無料でこれを取得できます。

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

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

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

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

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

于 2008-10-22T15:28:58.540 に答える
18

楽しみのために、List をリフレクターにポップした結果の 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 を繰り返し実行している場合、同じデリゲートを再利用するのではなく、より多くの使い捨てオブジェクトが作成されます。しかし、これは本当に特殊なケースです。通常の使用では、ほとんどまたはまったく違いが見られません。

于 2008-10-22T14:53:48.080 に答える
14

私は、それらを異なるものにする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 メソッドのアプローチには制限があります。yield return を使用して IEnumerable を実装している場合、ラムダ内で yield return を実行することはできません。したがって、この方法では、コレクション内の項目をループして戻り値を生成することはできません。foreach キーワードを使用し、ループ内で現在のループ値のコピーを手動で作成して、クロージャの問題を回避する必要があります。

詳細はこちら

于 2008-10-22T15:41:40.213 に答える
14

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

于 2008-10-22T14:22:48.750 に答える
6

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

2 番目は次のように記述できます。

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

私はどちらが好きで、多くの入力を節約します。

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

于 2008-10-22T14:24:52.630 に答える
5

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

List.ForEach()あなたがしたいことを言います。 foreach(item in list)また、あなたがそれをどのようにやりたいかを正確に言います。これにより、将来的にhow部分List.ForEachの実装を自由に変更できます。たとえば、仮想的な将来のバージョンの .Net は、この時点ですべての人が通常アイドル状態の CPU コアを多数持っているという仮定の下で、常に並行して実行される可能性があります。 List.ForEach

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


この問題に関する最近のコメントは、次の場所にあります。

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

于 2008-10-22T14:41:38.837 に答える
2

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

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

于 2008-10-22T14:26:44.857 に答える
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 (または必要に応じてループ for) を使用します。

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

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

次と同等です。

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

またはlabda式を使用:

(s) => <process the string>
于 2014-04-25T10:50:04.600 に答える
1

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

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

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

于 2008-10-22T14:25:43.580 に答える
1

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

于 2008-10-22T14:35:55.543 に答える