コレクションを反復処理する方法は明らかにたくさんあります。違いがあるのか、なぜ他の方法よりも1つの方法を使用するのか興味があります。
最初のタイプ:
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>
});
上記の匿名デリゲートの代わりに、再利用可能なデリゲートを指定できると思います...
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
ループ内のリストから項目を削除すると、副作用が生じる可能性があります。最も一般的なものは、この質問に対するコメントで説明されています。
一般に、リストから複数のアイテムを削除する場合は、実際の削除から削除するアイテムの決定を分離する必要があります。コードをコンパクトに保つことはできませんが、アイテムを見逃さないことが保証されます。
ここにいくつかのコードがあり(VS2005およびC#2.0)、以前のエンジニアが書いたすべてのコードにlist.ForEach( delegate(item) { foo;});
の代わりにforeach(item in list) {foo; };
を使用する方法を避けました。例えばdataReaderから行を読み取るためのコードブロック。
私はまだ彼らがなぜこれをしたのか正確に知りません。
list.ForEach()
の欠点は次のとおりです。
C#2.0ではより冗長です。ただし、C#3以降では、「=>
」構文を使用して、簡潔な式を作成できます。
あまり馴染みがありません。このコードを保守しなければならない人は、なぜあなたがそれをそのようにしたのか疑問に思うでしょう。多分作家を賢く見えるようにする以外には理由はないと判断するのにしばらく時間がかかりました(残りのコードの品質がそれを損なった)。また、デリゲートコードブロックの末尾に「})
」が含まれているため、読みにくくなりました。
ビルワーグナーの著書「効果的な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 "=>
"演算子でコンパクトに。そのような場合、ForEach()
よりも具体的なLINQ拡張メソッドが既に存在する可能性があります。 Where()
、Select()
、Any()
、All()
、Max()
、または他の多くのLINQメソッドのいずれかが、ループで必要な処理をまだ行っていないかどうかを確認します。
楽しみのために、リストをリフレクターにポップしました。これが結果の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を繰り返し実行している場合、同じデリゲートを再利用するのではなく、より多くのスローアウェイオブジェクトが作成されます-BUT-これは本当にフリンジケースです。通常の使用法では、違いはほとんどまたはまったくありません。
私はそれらを異なるものにする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キーワードを使用し、ループ内で現在のループ値のコピーを手動で作成することにより、クロージャ問題を回避する必要があります。
someList.ForEach()
呼び出しは簡単に並列化できますが、通常のforeach
は簡単に並列化できません。異なるコアでいくつかの異なるデリゲートを簡単に実行できますが、これは通常のforeach
では簡単ではありません。
ちょうど2セント
匿名デリゲートに名前を付けることができます:-)
そして、あなたは2番目を次のように書くことができます:
someList.ForEach(s => s.ToUpper())
私はそれを好み、多くのタイピングを節約します。
Joachimが言うように、並列処理は2番目の形式に適用する方が簡単です。
舞台裏では、匿名デリゲートが実際のメソッドに変換されるため、コンパイラが関数をインライン化することを選択しなかった場合、2番目の選択肢でオーバーヘッドが発生する可能性があります。さらに、匿名デリゲートの例の本体で参照されるローカル変数は、コンパイラーが新しいメソッドにコンパイルされるという事実を隠すためのトリックにより、本質的に変化します。 C#がこの魔法をどのように行うかについての詳細はこちら:
http://blogs.msdn.com/oldnewthing/archive/2006/08/04/688527.aspx
ForEachスコープ(デリゲート関数)全体が1行のコード(関数を呼び出す)として扱われ、ブレークポイントを設定したり、コードにステップインしたりすることはできません。未処理の例外が発生すると、ブロック全体がマークされます。
List.ForEach()はより機能的であると見なされます。
List.ForEach()
は、あなたがしたいことを言います。 foreach(item in list)
はまた、あなたがそれをしたい方法を正確に言います。これにより、List.ForEach
は将来的にhow部分の実装を自由に変更できます。たとえば、.Netの仮想の将来のバージョンは、この時点で誰もが一般的にアイドル状態にあるCPUコアを多数持っているという仮定の下で、List.ForEach
を常に並行して実行します。
一方、foreach (item in list)
を使用すると、ループをもう少し制御できます。たとえば、アイテムは何らかの順序で繰り返されることがわかっているので、アイテムが条件を満たしている場合は簡単に途中で中断できます。
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>
}
またはlabda式を使用して:
(s) => <process the string>
2番目の方法では、拡張メソッドを使用して、リスト内の各要素に対してデリゲートメソッドを実行します。
このように、別のデリゲート(= method)呼び出しがあります。
さらに、リストをforループで反復する可能性があります。
注意すべきことの1つは、Generic .ForEachメソッドを終了する方法です。 この説明 を参照してください。リンクは、この方法が最速だと言っているようですが。理由は定かではありません-コンパイルすると同等になると思います...