コレクションを反復処理するためにforeachループではなくIEnumeratorを使用することが有利な場合があるかどうか疑問に思いました。たとえば、次のコードサンプルのいずれかを他のサンプルよりも使用したほうがよい場合はありますか?
IEnumerator<MyClass> classesEnum = myClasses.GetEnumerator();
while(classesEnum.MoveNext())
Console.WriteLine(classesEnum.Current);
の代わりに
foreach (var class in myClasses)
Console.WriteLine(class);
まず、例(foreach
とGetEnumerator
の間)の大きな違いの1つは、イテレーターがイテレーターの場合、foreach
がイテレーターでDispose()
を呼び出すことを保証することです。はIDisposable
です。これは、多くのイテレータにとって重要です(たとえば、外部データフィードを消費している可能性があります)。
実際、foreach
が私たちが望むほど役に立たない場合があります。
まず、「最初の項目」のケースについて説明します ここでは(foreach /最初の反復の検出) 。
しかし、より多くの; 2つの列挙可能なシーケンスをつなぎ合わせるために欠落しているZip
メソッド(またはSequenceEqual
メソッド)を書き込もうとすると、両方のシーケンスでforeach
を使用できることがわかります。クロスジョインを実行します。それらの1つに直接イテレータを使用する必要があります。
static IEnumerable<T> Zip<T>(this IEnumerable<T> left,
IEnumerable<T> right)
{
using (var iter = right.GetEnumerator())
{
// consume everything in the first sequence
foreach (var item in left)
{
yield return item;
// and add an item from the second sequnce each time (if we can)
if (iter.MoveNext())
{
yield return iter.Current;
}
}
// any remaining items in the second sequence
while (iter.MoveNext())
{
yield return iter.Current;
}
}
}
static bool SequenceEqual<T>(this IEnumerable<T> left,
IEnumerable<T> right)
{
var comparer = EqualityComparer<T>.Default;
using (var iter = right.GetEnumerator())
{
foreach (var item in left)
{
if (!iter.MoveNext()) return false; // first is longer
if (!comparer.Equals(item, iter.Current))
return false; // item different
}
if (iter.MoveNext()) return false; // second is longer
}
return true; // same length, all equal
}
C#言語仕様 によると:
フォームのforeachステートメント
foreach (V v in x) embedded-statement
次に、次のように展開されます。
{
E e = ((C)(x)).GetEnumerator();
try {
V v;
while (e.MoveNext()) {
v = (V)(T)e.Current;
embedded-statement
}
}
finally {
... // Dispose e
}
}
2つの例の本質は同じですが、いくつかの重要な違いがあります。特に、try
/finally
です。
C#では、foreach
は上記で投稿したIEnumerator
コードとまったく同じことをしています。 foreach
構造は、プログラマーが簡単に使用できるように提供されています。どちらの場合も生成されるILは同じ/類似していると思います。
最初の反復が他とは異なることをするときに、私は時々それを使用しました。
たとえば、メンバーをコンソールに出力し、結果を行ごとに区切る場合は、次のように記述できます。
using (IEnumerator<MyClass> classesEnum = myClasses.GetEnumerator()) {
if (classEnum.MoveNext())
Console.WriteLine(classesEnum.Current);
while (classesEnum.MoveNext()) {
Console.WriteLine("-----------------");
Console.WriteLine(classesEnum.Current);
}
}
その結果は
myClass 1
-----------------
myClass 2
-----------------
myClass 3
また、別の状況は、2つの列挙子を一緒に反復する場合です。
using (IEnumerator<MyClass> classesEnum = myClasses.GetEnumerator()) {
using (IEnumerator<MyClass> classesEnum2 = myClasses2.GetEnumerator()) {
while (classesEnum.MoveNext() && classEnum2.MoveNext())
Console.WriteLine("{0}, {1}", classesEnum.Current, classesEnum2.Current);
}
}
結果
myClass 1, myClass A
myClass 2, myClass B
myClass 3, myClass C
列挙子を使用すると、反復をより柔軟に行うことができます。ただし、ほとんどの場合、foreachを使用できます