反復子 (Visual Basic)

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

iterator メソッドまたは get アクセサーは、コレクションに対するカスタム イテレーションを実行します。 iterator メソッドは、Yield ステートメントを使用して、各要素を 1 回に 1 つ返します。 Yield ステートメントに達すると、コードの現在の場所が記憶されます。 次回、iterator 関数が呼び出されると、この位置から実行が再開されます。

For Each…Next ステートメントまたは LINQ クエリを使用して、クライアント コードから反復子を呼び出します。

次の例では、For Each ループの最初の反復子により、最初の Yield ステートメントに達するまで SomeNumbers iterator メソッドで実行が続行されます。 このイテレーションは 3 の値を返し、iterator メソッドの現在の場所が保持されます。 ループの次のイテレーションでは、iterator メソッドの実行が中断した場所から続行し、Yield ステートメントに達したときに再度停止します。 このイテレーションは 5 の値を返し、ここでも iterator メソッドの現在の場所が保持されます。 iterator メソッドの最後に達すると、ループが完了します。

Sub Main()
    For Each number As Integer In SomeNumbers()
        Console.Write(number & " ")
    Next
    ' Output: 3 5 8
    Console.ReadKey()
End Sub

Private Iterator Function SomeNumbers() As System.Collections.IEnumerable
    Yield 3
    Yield 5
    Yield 8
End Function

Iterator メソッドまたは get アクセサーの戻り値の型は、IEnumerableIEnumerable<T>IEnumerator、または IEnumerator<T> となります。

Exit Function または Return ステートメントを使用すると、反復を終了できます。

Visual Basic の iterator 関数と get アクセサー宣言には、Iterator 修飾子が含まれています。

反復子は、Visual Studio 2012 の Visual Basic で導入されました。

Note

この記事の単純な反復子の例を除くすべての例には、System.Collections および System.Collections.Generic 名前空間の Imports ステートメントが含まれています。

単純な反復子

次の例では、For…Next ループ内に 1 つの Yield ステートメントが含まれます。 Main では、For Each ステートメント本文の各イテレーションで iterator 関数が呼び出され、これが次の Yield ステートメントに続行されます。

Sub Main()
    For Each number As Integer In EvenSequence(5, 18)
        Console.Write(number & " ")
    Next
    ' Output: 6 8 10 12 14 16 18
    Console.ReadKey()
End Sub

Private Iterator Function EvenSequence(
ByVal firstNumber As Integer, ByVal lastNumber As Integer) _
As System.Collections.Generic.IEnumerable(Of Integer)

    ' Yield even numbers in the range.
    For number As Integer = firstNumber To lastNumber
        If number Mod 2 = 0 Then
            Yield number
        End If
    Next
End Function

コレクション クラスを作成する

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

GetEnumerator メソッドは、Yield ステートメントを使用して各文字列を 1 つずつ返すもので、関数の宣言には Iterator 修飾子が存在します。

Sub Main()
    Dim days As New DaysOfTheWeek()
    For Each day As String In days
        Console.Write(day & " ")
    Next
    ' Output: Sun Mon Tue Wed Thu Fri Sat
    Console.ReadKey()
End Sub

Private Class DaysOfTheWeek
    Implements IEnumerable

    Public days =
        New String() {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}

    Public Iterator Function GetEnumerator() As IEnumerator _
        Implements IEnumerable.GetEnumerator

        ' Yield each day of the week.
        For i As Integer = 0 To days.Length - 1
            Yield days(i)
        Next
    End Function
End Class

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

クラス インスタンス (theZoo) を参照する For Each ステートメントでは、GetEnumerator メソッドが暗黙的に呼び出されます。 Birds および Mammals プロパティを参照する For Each ステートメントでは、AnimalsForType という名前の iterator メソッドが使用されます。

Sub Main()
    Dim theZoo As New Zoo()

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

    For Each name As String In theZoo
        Console.Write(name & " ")
    Next
    Console.WriteLine()
    ' Output: Whale Rhinoceros Penguin Warbler

    For Each name As String In theZoo.Birds
        Console.Write(name & " ")
    Next
    Console.WriteLine()
    ' Output: Penguin Warbler

    For Each name As String In theZoo.Mammals
        Console.Write(name & " ")
    Next
    Console.WriteLine()
    ' Output: Whale Rhinoceros

    Console.ReadKey()
End Sub

Public Class Zoo
    Implements IEnumerable

    ' Private members.
    Private animals As New List(Of Animal)

    ' Public methods.
    Public Sub AddMammal(ByVal name As String)
        animals.Add(New Animal With {.Name = name, .Type = Animal.TypeEnum.Mammal})
    End Sub

    Public Sub AddBird(ByVal name As String)
        animals.Add(New Animal With {.Name = name, .Type = Animal.TypeEnum.Bird})
    End Sub

    Public Iterator Function GetEnumerator() As IEnumerator _
        Implements IEnumerable.GetEnumerator

        For Each theAnimal As Animal In animals
            Yield theAnimal.Name
        Next
    End Function

    ' Public members.
    Public ReadOnly Property Mammals As IEnumerable
        Get
            Return AnimalsForType(Animal.TypeEnum.Mammal)
        End Get
    End Property

    Public ReadOnly Property Birds As IEnumerable
        Get
            Return AnimalsForType(Animal.TypeEnum.Bird)
        End Get
    End Property

    ' Private methods.
    Private Iterator Function AnimalsForType( _
    ByVal type As Animal.TypeEnum) As IEnumerable
        For Each theAnimal As Animal In animals
            If (theAnimal.Type = type) Then
                Yield theAnimal.Name
            End If
        Next
    End Function

    ' Private class.
    Private Class Animal
        Public Enum TypeEnum
            Bird
            Mammal
        End Enum

        Public Property Name As String
        Public Property Type As TypeEnum
    End Class
End Class

Try ブロック

Visual Basic では、Try...Catch...Finally ステートメントTry ブロックで Yield ステートメントを使用できます。 Yield ステートメントがある Try ブロックには、Catch ブロックと Finally ブロックを記述することができます。

次の例では、iterator 関数の中に TryCatchFinally の各ブロックが存在します。 iterator 関数内の Finally ブロックは、For Each の反復が完了する前に実行されます。

Sub Main()
    For Each number As Integer In Test()
        Console.WriteLine(number)
    Next
    Console.WriteLine("For Each is done.")

    ' Output:
    '  3
    '  4
    '  Something happened. Yields are done.
    '  Finally is called.
    '  For Each is done.
    Console.ReadKey()
End Sub

Private Iterator Function Test() As IEnumerable(Of Integer)
    Try
        Yield 3
        Yield 4
        Throw New Exception("Something happened. Yields are done.")
        Yield 5
        Yield 6
    Catch ex As Exception
        Console.WriteLine(ex.Message)
    Finally
        Console.WriteLine("Finally is called.")
    End Try
End Function

Yield ステートメントを Catch ブロックや Finally ブロックに記述することはできません。

(iterator メソッドではなく) For Each 本体で例外がスローされた場合、iterator 関数の Catch ブロックは実行されず、iterator 関数の Finally ブロックが実行されます。 iterator 関数内の Catch ブロックでキャッチされるのは、iterator 関数内で発生した例外だけです。

匿名メソッド

Visual Basic では、iterator 関数として匿名関数を使用することができます。 次に例を示します。

Dim iterateSequence = Iterator Function() _
                      As IEnumerable(Of Integer)
                          Yield 1
                          Yield 2
                      End Function

For Each number As Integer In iterateSequence()
    Console.Write(number & " ")
Next
' Output: 1 2
Console.ReadKey()

次の例に示したのは、iterator メソッドではない、引数を検証するメソッドです。 このメソッドは、コレクション要素を表す匿名反復子の結果を返します。

Sub Main()
    For Each number As Integer In GetSequence(5, 10)
        Console.Write(number & " ")
    Next
    ' Output: 5 6 7 8 9 10
    Console.ReadKey()
End Sub

Public Function GetSequence(ByVal low As Integer, ByVal high As Integer) _
As IEnumerable
    ' Validate the arguments.
    If low < 1 Then
        Throw New ArgumentException("low is too low")
    End If
    If high > 140 Then
        Throw New ArgumentException("high is too high")
    End If

    ' Return an anonymous iterator function.
    Dim iterateSequence = Iterator Function() As IEnumerable
                              For index = low To high
                                  Yield index
                              Next
                          End Function
    Return iterateSequence()
End Function

このようにせず、検証を iterator 関数内で行った場合、For Each 本体の最初の反復の開始まで検証を実行できません。

ジェネリック リストと共に反復子を使用する

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

ジェネリック メソッド GetEnumerator だけでなく、非ジェネリック メソッド GetEnumerator も実装する必要があります。 これは、IEnumerable<T>IEnumerable から継承するためです。 非ジェネリック実装は、ジェネリック実装に従います。

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

BottomToTop プロパティの宣言には、Iterator キーワードが含まれます。

Sub Main()
    Dim theStack As New Stack(Of Integer)

    ' Add items to the stack.
    For number As Integer = 0 To 9
        theStack.Push(number)
    Next

    ' Retrieve items from the stack.
    ' For Each is allowed because theStack implements
    ' IEnumerable(Of Integer).
    For Each number As Integer In theStack
        Console.Write("{0} ", number)
    Next
    Console.WriteLine()
    ' Output: 9 8 7 6 5 4 3 2 1 0

    ' For Each is allowed, because theStack.TopToBottom
    ' returns IEnumerable(Of Integer).
    For Each number As Integer In theStack.TopToBottom
        Console.Write("{0} ", number)
    Next
    Console.WriteLine()
    ' Output: 9 8 7 6 5 4 3 2 1 0

    For Each number As Integer In theStack.BottomToTop
        Console.Write("{0} ", number)
    Next
    Console.WriteLine()
    ' Output: 0 1 2 3 4 5 6 7 8 9

    For Each number As Integer In theStack.TopN(7)
        Console.Write("{0} ", number)
    Next
    Console.WriteLine()
    ' Output: 9 8 7 6 5 4 3

    Console.ReadKey()
End Sub

Public Class Stack(Of T)
    Implements IEnumerable(Of T)

    Private values As T() = New T(99) {}
    Private top As Integer = 0

    Public Sub Push(ByVal t As T)
        values(top) = t
        top = top + 1
    End Sub

    Public Function Pop() As T
        top = top - 1
        Return values(top)
    End Function

    ' This function implements the GetEnumerator method. It allows
    ' an instance of the class to be used in a For Each statement.
    Public Iterator Function GetEnumerator() As IEnumerator(Of T) _
        Implements IEnumerable(Of T).GetEnumerator

        For index As Integer = top - 1 To 0 Step -1
            Yield values(index)
        Next
    End Function

    Public Iterator Function GetEnumerator1() As IEnumerator _
        Implements IEnumerable.GetEnumerator

        Yield GetEnumerator()
    End Function

    Public ReadOnly Property TopToBottom() As IEnumerable(Of T)
        Get
            Return Me
        End Get
    End Property

    Public ReadOnly Iterator Property BottomToTop As IEnumerable(Of T)
        Get
            For index As Integer = 0 To top - 1
                Yield values(index)
            Next
        End Get
    End Property

    Public Iterator Function TopN(ByVal itemsFromTop As Integer) _
        As IEnumerable(Of T)

        ' Return less than itemsFromTop if necessary.
        Dim startIndex As Integer =
            If(itemsFromTop >= top, 0, top - itemsFromTop)

        For index As Integer = top - 1 To startIndex Step -1
            Yield values(index)
        Next
    End Function
End Class

構文情報

反復子は、メソッドまたは get アクセサーとして指定できます。 反復子を、イベント、インスタンス コンストラクター、静的コンストラクター、静的デストラクターで指定することはできません。

Yield ステートメント内の式の型から反復子の戻り値の型への暗黙的な変換が存在する必要があります。

Visual Basic の場合、iterator メソッドで ByRef パラメーターを指定することはできません。

Visual Basic の場合、"Yield" は予約語ではなく、Iterator メソッドまたは get アクセサーで使用される場合にのみ、特別な意味を持ちます。

技術的な実装

メソッドとして反復子を記述しても、コンパイラが入れ子のクラス (つまり、事実上、ステート マシン) に変換します。 このクラスは、クライアント コードで For Each...Next ループが続く限り、反復子の位置を追跡します。

コンパイラの動作を確認するには、Ildasm.exe ツールを使用して、iterator メソッドに対して生成される Microsoft 中間言語コードを表示します。

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

For Each…Next ループの連続する反復ごとに (または IEnumerator.MoveNext を直接呼び出すと)、前の Yield ステートメントの後で次の反復子コード本体が再開されます。 その後、反復子本体の最後に到達するか、Exit Function または Return ステートメントが検出されるまで、次の Yield ステートメントに続行されます。

反復子は、IEnumerator.Reset メソッドをサポートしません。 反復処理を最初から再度行う場合は、新しい反復子を取得する必要があります。

詳細については、「Visual Basic 言語の仕様」を参照してください。

反復子の使用

反復子を使用すると、複雑なコードを使用して一覧シーケンスを設定する必要がある場合に、For Each ループの単純さを維持することができます。 これは次のような場合に役立ちます。

  • 最初の For Each ループ イテレーションの後に一覧シーケンスを変更する。

  • 最初の For Each ループ イテレーションの前に大きい一覧が完全に読み込まれないようにする。 例として、ページ フェッチでのテーブル行のバッチの読み込みなどがあります。 また、別の例として、EnumerateFiles メソッドでの .NET Framework 内の反復子の実装があります。

  • 反復子に一覧の作成をカプセル化する。 iterator メソッドでは、一覧を作成してから、ループで各結果を生成することができます。

関連項目