次の方法で共有


反復子 (Visual Basic)

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

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

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

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

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

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

Exit FunctionまたはReturnステートメントを使用して、イテレーションを終了できます。

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

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

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

単純な反復子

次の例では、For..内に 1 つの Yield ステートメントがあります 。次の ループ。 Mainでは、For Eachステートメント本体の各反復処理で反復子関数の呼び出しが作成され、次の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 クラスは、IEnumerable メソッドを必要とするGetEnumerator インターフェイスを実装します。 コンパイラは、GetEnumeratorを返すIEnumerator メソッドを暗黙的に呼び出します。

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 クラスを作成します。

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

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

ブロックを試す

Visual Basic では、Try の Try ブロックでYieldステートメントを使用できます。捕まえる。。。Finally ステートメントYield ステートメントを持つTry ブロックには、Catch ブロックを含め、Finally ブロックを含めることができます。

次の例には、反復子関数の TryCatch、および Finally ブロックが含まれています。 反復子関数の 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 ブロック内にすることはできません。

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

匿名メソッド

Visual Basic では、匿名関数を反復子関数にすることができます。 この例を次に示します。

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()

次の例には、引数を検証する非反復子メソッドがあります。 このメソッドは、コレクション要素を記述する匿名反復子の結果を返します。

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

検証が反復子関数内にある場合は、 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 では、反復子メソッドに ByRef パラメーターを指定することはできません。

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

技術的な実装

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

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

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

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

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

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

反復子の使用

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

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

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

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

こちらも参照ください