次の方法で共有


反復子 (C#)

反復子を使用して、リストや配列などのコレクションをステップ実行できます。

反復子メソッドまたは get アクセサーは、コレクションに対してカスタムイテレーションを実行します。 反復子メソッドは 、yield return ステートメントを使用して各要素を一度に 1 つずつ返します。 yield returnステートメントに達すると、コード内の現在の場所が記憶されます。 次に反復子関数が呼び出されるときに、その場所から実行が再開されます。

foreach ステートメントを使用するか、LINQ クエリを使用して、クライアント コードから反復子を使用します。

次の例では、foreach ループの最初の反復により、最初の SomeNumbers ステートメントに到達するまで、yield return反復子メソッドで実行が続行されます。 このイテレーションは 3 の値を返し、反復子メソッドの現在の位置は保持されます。 ループの次の繰り返しでは、反復子メソッドでの実行は中断したところから継続し、 yield return ステートメントに達すると再び停止します。 このイテレーションでは値 5 が返され、反復子メソッドの現在の位置が再び保持されます。 反復子メソッドの末尾に達すると、ループが完了します。

static void Main()
{
    foreach (int number in SomeNumbers())
    {
        Console.Write(number.ToString() + " ");
    }
    // Output: 3 5 8
    Console.ReadKey();
}

public static System.Collections.IEnumerable SomeNumbers()
{
    yield return 3;
    yield return 5;
    yield return 8;
}

反復子メソッドまたは get アクセサーの戻り値の型は、 IEnumerableIEnumerable<T>IEnumerator、または IEnumerator<T>できます。

yield break ステートメントを使用して、イテレーションを終了できます。

単純反復子の例を除くこのトピックのすべての例では、およびSystem.Collections名前空間の System.Collections.Generic ディレクティブを含めます。

単純な反復子

次の例では、yield return ループ内に 1 つのステートメントがあります。 Mainでは、foreachステートメント本体の各反復処理で反復子関数の呼び出しが作成され、次のyield return ステートメントに進みます。

static void Main()
{
    foreach (int number in EvenSequence(5, 18))
    {
        Console.Write(number.ToString() + " ");
    }
    // Output: 6 8 10 12 14 16 18
    Console.ReadKey();
}

public static System.Collections.Generic.IEnumerable<int>
    EvenSequence(int firstNumber, int lastNumber)
{
    // Yield even numbers in the range.
    for (int number = firstNumber; number <= lastNumber; number++)
    {
        if (number % 2 == 0)
        {
            yield return number;
        }
    }
}

コレクション クラスの作成

次の例では、DaysOfTheWeek クラスは、IEnumerable メソッドを必要とするGetEnumerator インターフェイスを実装します。 コンパイラは、GetEnumeratorを返すIEnumerator メソッドを暗黙的に呼び出します。

GetEnumerator メソッドは、yield return ステートメントを使用して、各文字列を一度に 1 つずつ返します。

static void Main()
{
    DaysOfTheWeek days = new DaysOfTheWeek();

    foreach (string day in days)
    {
        Console.Write(day + " ");
    }
    // Output: Sun Mon Tue Wed Thu Fri Sat
    Console.ReadKey();
}

public class DaysOfTheWeek : IEnumerable
{
    private string[] days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];

    public IEnumerator GetEnumerator()
    {
        for (int index = 0; index < days.Length; index++)
        {
            // Yield each day of the week.
            yield return days[index];
        }
    }
}

次の例では、動物のコレクションを含む Zoo クラスを作成します。

クラス インスタンス (foreach) を参照する theZoo ステートメントは、GetEnumerator メソッドを暗黙的に呼び出します。 foreachプロパティとBirds プロパティを参照するMammals ステートメントでは、名前付き反復子メソッドAnimalsForType使用されます。

static void Main()
{
    Zoo theZoo = new Zoo();

    theZoo.AddMammal("Whale");
    theZoo.AddMammal("Rhinoceros");
    theZoo.AddBird("Penguin");
    theZoo.AddBird("Warbler");

    foreach (string name in theZoo)
    {
        Console.Write(name + " ");
    }
    Console.WriteLine();
    // Output: Whale Rhinoceros Penguin Warbler

    foreach (string name in theZoo.Birds)
    {
        Console.Write(name + " ");
    }
    Console.WriteLine();
    // Output: Penguin Warbler

    foreach (string name in theZoo.Mammals)
    {
        Console.Write(name + " ");
    }
    Console.WriteLine();
    // Output: Whale Rhinoceros

    Console.ReadKey();
}

public class Zoo : IEnumerable
{
    // Private members.
    private List<Animal> animals = new List<Animal>();

    // Public methods.
    public void AddMammal(string name)
    {
        animals.Add(new Animal { Name = name, Type = Animal.TypeEnum.Mammal });
    }

    public void AddBird(string name)
    {
        animals.Add(new Animal { Name = name, Type = Animal.TypeEnum.Bird });
    }

    public IEnumerator GetEnumerator()
    {
        foreach (Animal theAnimal in animals)
        {
            yield return theAnimal.Name;
        }
    }

    // Public members.
    public IEnumerable Mammals
    {
        get { return AnimalsForType(Animal.TypeEnum.Mammal); }
    }

    public IEnumerable Birds
    {
        get { return AnimalsForType(Animal.TypeEnum.Bird); }
    }

    // Private methods.
    private IEnumerable AnimalsForType(Animal.TypeEnum type)
    {
        foreach (Animal theAnimal in animals)
        {
            if (theAnimal.Type == type)
            {
                yield return theAnimal.Name;
            }
        }
    }

    // Private class.
    private class Animal
    {
        public enum TypeEnum { Bird, Mammal }

        public string Name { get; set; }
        public TypeEnum Type { get; set; }
    }
}

ジェネリック リストでの反復子の使用

次の例では、 Stack<T> ジェネリック クラスは、 IEnumerable<T> ジェネリック インターフェイスを実装します。 Push メソッドは、T型の配列に値を割り当てます。 GetEnumerator メソッドは、yield return ステートメントを使用して配列値を返します。

ジェネリック GetEnumerator メソッドに加えて、非ジェネリック GetEnumerator メソッドも実装する必要があります。 これは、 IEnumerable<T>IEnumerableから継承されるためです。 非ジェネリック実装は、ジェネリック実装に委ねます。

この例では、名前付き反復子を使用して、同じデータコレクションを反復処理するさまざまな方法をサポートしています。 これらの名前付き反復子は、 TopToBottom プロパティと BottomToTop プロパティ、および TopN メソッドです。

BottomToTop プロパティは、get アクセサーで反復子を使用します。

static void Main()
{
    Stack<int> theStack = new Stack<int>();

    //  Add items to the stack.
    for (int number = 0; number <= 9; number++)
    {
        theStack.Push(number);
    }

    // Retrieve items from the stack.
    // foreach is allowed because theStack implements IEnumerable<int>.
    foreach (int number in theStack)
    {
        Console.Write("{0} ", number);
    }
    Console.WriteLine();
    // Output: 9 8 7 6 5 4 3 2 1 0

    // foreach is allowed, because theStack.TopToBottom returns IEnumerable(Of Integer).
    foreach (int number in theStack.TopToBottom)
    {
        Console.Write("{0} ", number);
    }
    Console.WriteLine();
    // Output: 9 8 7 6 5 4 3 2 1 0

    foreach (int number in theStack.BottomToTop)
    {
        Console.Write("{0} ", number);
    }
    Console.WriteLine();
    // Output: 0 1 2 3 4 5 6 7 8 9

    foreach (int number in theStack.TopN(7))
    {
        Console.Write("{0} ", number);
    }
    Console.WriteLine();
    // Output: 9 8 7 6 5 4 3

    Console.ReadKey();
}

public class Stack<T> : IEnumerable<T>
{
    private T[] values = new T[100];
    private int top = 0;

    public void Push(T t)
    {
        values[top] = t;
        top++;
    }
    public T Pop()
    {
        top--;
        return values[top];
    }

    // This method implements the GetEnumerator method. It allows
    // an instance of the class to be used in a foreach statement.
    public IEnumerator<T> GetEnumerator()
    {
        for (int index = top - 1; index >= 0; index--)
        {
            yield return values[index];
        }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    public IEnumerable<T> TopToBottom
    {
        get { return this; }
    }

    public IEnumerable<T> BottomToTop
    {
        get
        {
            for (int index = 0; index <= top - 1; index++)
            {
                yield return values[index];
            }
        }
    }

    public IEnumerable<T> TopN(int itemsFromTop)
    {
        // Return less than itemsFromTop if necessary.
        int startIndex = itemsFromTop >= top ? 0 : top - itemsFromTop;

        for (int index = top - 1; index >= startIndex; index--)
        {
            yield return values[index];
        }
    }

}

構文情報

反復子は、メソッドまたは get アクセサーとして発生する可能性があります。 反復子は、イベント、インスタンス コンストラクター、静的コンストラクター、または静的ファイナライザーでは発生できません。

yield return ステートメント内の式型から反復子によって返されるIEnumerable<T>の型引数への暗黙的な変換が存在する必要があります。

C# では、反復子メソッドに inref、または out パラメーターを含めることはできません。

C# では、 yield は予約語ではなく、 return または break キーワードの前に使用される場合にのみ特別な意味を持ちます。

技術的な実装

反復子はメソッドとして記述しますが、コンパイラは、実際にはステート マシンである入れ子になったクラスに変換します。 このクラスは、クライアント コード内の foreach ループが継続する限り、反復子の位置を追跡します。

コンパイラの動作を確認するには、Ildasm.exe ツールを使用して、反復子メソッド用に生成された共通の中間言語コードを表示できます。

クラスまたは構造体の反復子を作成するときに、IEnumerator インターフェイス全体を実装する必要はありません。 コンパイラは、反復子を検出すると、CurrentまたはMoveNext インターフェイスのDisposeIEnumerator、およびIEnumerator<T>メソッドを自動的に生成します。

foreach ループの反復 (または IEnumerator.MoveNext への直接呼び出し) のたびに、前のyield returnステートメントの後に次の反復子コード本体が再開されます。 次に、反復子本体の末尾に達するか、yield returnステートメントが検出されるまで、次のyield break ステートメントに進みます。

反復子は、 IEnumerator.Reset メソッドをサポートしていません。 最初から繰り返すには、新しい反復子を取得する必要があります。 反復子メソッドによって返される反復子に対して Reset を呼び出すと、NotSupportedException がスローされます。

詳細については、 C# 言語仕様を参照してください。

反復子の使用

反復子を使用すると、複雑なコードを使用してリスト シーケンスを設定する必要がある場合に、 foreach ループの簡略化を維持できます。 これは、次の操作を行う場合に役立ちます。

  • 最初の foreach ループイテレーションの後にリスト シーケンスを変更します。

  • foreach ループの最初の反復処理の前に、大きなリストを完全に読み込まないようにします。 たとえば、一度にまとめてテーブル行を読み込むためのページ単位の取得が考えられます。 もう 1 つの例は、.NET で反復子を実装する EnumerateFiles メソッドです。

  • 反復子内のリストの構築をカプセル化します。 反復子メソッドでは、リストを作成し、各結果をループに生成できます。

こちらも参照ください