チュートリアル: Visual Basic での IEnumerable(Of T) の実装

IEnumerable<T> インターフェイスは、値のシーケンスを、一度に 1 項目ずつ返すことができるクラスによって実装されます。 一度に 1 項目ずつデータを返す利点は、データ セット全体をメモリに読み込んで操作する必要がないことです。 データから 1 つの項目を読み込むのに必要なメモリを使用するだけで済みます。 IEnumerable(T) インターフェイスを実装するクラスを、For Each ループまたは LINQ クエリで使用できます。

たとえば、大きなテキスト ファイルを読み取り、そのファイルから、特定の検索条件に一致する各行を返す必要があるアプリケーションがあるとします。 アプリケーションは LINQ クエリを使用して、指定された条件に一致する行をファイルから返します。 LINQ クエリを使用してファイルのコンテンツにクエリを実行するために、アプリケーションでは、ファイルのコンテンツを配列またはコレクションに読み込むことができます。 ただし、ファイル全体を配列またはコレクションに読み込むと、必要以上にメモリが消費されます。 LINQ クエリは、列挙可能なクラスを使用してファイルのコンテンツにクエリを実行し、検索条件に一致する値のみを返すことができます。 一致する値だけをいくつか返すクエリの場合、消費メモリは格段に少なくなります。

IEnumerable<T> インターフェイスを実装するクラスを作成して、ソース データを、列挙可能なデータとして公開できます。 IEnumerable(T) インターフェイスを実装するクラスには、ソース データを反復処理するために IEnumerator<T> インターフェイスを実装する別のクラスが必要です。 この 2 つのクラスを使用すると、データの項目を特定の型として順番に返すことができます。

このチュートリアルでは、IEnumerable(Of String) インターフェイスを実装するクラスと IEnumerator(Of String) インターフェイスを実装するクラスを作成して、テキスト ファイルを 1 行ずつ読み取ります。

注意

次の手順で参照している Visual Studio ユーザー インターフェイス要素の一部は、お使いのコンピューターでは名前や場所が異なる場合があります。 これらの要素は、使用している Visual Studio のエディションや独自の設定によって決まります。 詳細については、「IDE をカスタマイズする」をご覧ください。

列挙可能なクラスの作成

列挙可能なクラス プロジェクトを作成する

  1. Visual Basic で、 [ファイル] メニューの [新規作成] をポイントし、 [プロジェクト] をクリックします。

  2. [新しいプロジェクト] ダイアログ ボックスの [プロジェクトの種類] ペインで、 [Windows] が選択されていることを確認します。 [テンプレート] ペインで [クラス ライブラリ] を選択します。 [名前] ボックスに StreamReaderEnumerable と入力して、 [OK] をクリックします。 新しいプロジェクトが表示されます。

  3. ソリューション エクスプローラーで、Class1.vb ファイルを右クリックし、 [名前の変更] をクリックします。 ファイルの名前を StreamReaderEnumerable.vb に変更し、Enter キーを押します。 ファイルの名前を変更すると、クラスの名前も StreamReaderEnumerable に変更されます。 このクラスが IEnumerable(Of String) インターフェイスを実装します。

  4. StreamReaderEnumerable プロジェクトを右クリックして [追加] をポイントし、 [新しい項目] をクリックします。 [クラス] テンプレートを選択します。 [名前] ボックスに「StreamReaderEnumerator.vb」と入力し、 [OK] をクリックします。

このプロジェクトの最初のクラスは列挙可能なクラスであり、IEnumerable(Of String) インターフェイスを実装します。 このジェネリック インターフェイスは IEnumerable インターフェイスを実装し、このクラスのコンシューマーが、String として型指定された値にアクセスできることを保証します。

IEnumerable を実装するコードを追加する

  1. StreamReaderEnumerable.vb ファイルを開きます。

  2. Public Class StreamReaderEnumerable の後の行に以下を入力し、Enter キーを押します。

    Implements IEnumerable(Of String)
    

    IEnumerable(Of String) インターフェイスに必要なメンバーが、クラスに自動入力されます。

  3. この列挙可能なクラスは、テキスト ファイルから 1 行ずつ行を読み取ります。 次のコードをクラスに追加して、ファイル パスを入力パラメーターとして取るパブリック コンストラクターを公開します。

    Private _filePath As String
    
    Public Sub New(ByVal filePath As String)
        _filePath = filePath
    End Sub
    
  4. IEnumerable(Of String) インターフェイスの GetEnumerator メソッドの実装は、StreamReaderEnumerator クラスの新しいインスタンスを返します。 IEnumerable(Of String) インターフェイスのメンバーのみを公開する必要があるため、IEnumerable インターフェイスの GetEnumerator メソッドの実装は Private にできます。 GetEnumerator メソッド用に生成されたコードを次のコードに置き換えます。

    Public Function GetEnumerator() As IEnumerator(Of String) _
        Implements IEnumerable(Of String).GetEnumerator
    
        Return New StreamReaderEnumerator(_filePath)
    End Function
    
    Private Function GetEnumerator1() As IEnumerator _
        Implements IEnumerable.GetEnumerator
    
        Return Me.GetEnumerator()
    End Function
    

IEnumerator を実装するコードを追加する

  1. StreamReaderEnumerator.vb ファイルを開きます。

  2. Public Class StreamReaderEnumerator の後の行に以下を入力し、Enter キーを押します。

    Implements IEnumerator(Of String)
    

    IEnumerator(Of String) インターフェイスに必要なメンバーが、クラスに自動入力されます。

  3. 列挙子クラスはテキスト ファイルを開き、ファイルから行を読み取るためにファイル I/O を実行します。 次のコードをクラスに追加して、ファイル パスを入力パラメーターとして取るパブリック コンストラクターを公開し、読み取るテキスト ファイルを開きます。

    Private _sr As IO.StreamReader
    
    Public Sub New(ByVal filePath As String)
        _sr = New IO.StreamReader(filePath)
    End Sub
    
  4. IEnumerator(Of String) インターフェイスと IEnumerator インターフェイスの両方の Current プロパティが、テキスト ファイルから現在の項目を String として返します。 IEnumerator(Of String) インターフェイスのメンバーのみを公開する必要があるため、IEnumerator インターフェイスの Current プロパティの実装は Private にできます。 Current プロパティ用に生成されたコードを次のコードに置き換えます。

    Private _current As String
    
    Public ReadOnly Property Current() As String _
        Implements IEnumerator(Of String).Current
    
        Get
            If _sr Is Nothing OrElse _current Is Nothing Then
                Throw New InvalidOperationException()
            End If
    
            Return _current
        End Get
    End Property
    
    Private ReadOnly Property Current1() As Object _
        Implements IEnumerator.Current
    
        Get
            Return Me.Current
        End Get
    End Property
    
  5. IEnumerator インターフェイスの MoveNext メソッドは、テキスト ファイル内の次の項目に移動し、Current プロパティによって返される値を更新します。 読み取る項目がない場合、MoveNext メソッドは False を返します。それ以外の場合、MoveNext メソッドは True を返します。 MoveNext メソッドに次のコードを追加します。

    Public Function MoveNext() As Boolean _
        Implements System.Collections.IEnumerator.MoveNext
    
        _current = _sr.ReadLine()
        If _current Is Nothing Then Return False
        Return True
    End Function
    
  6. IEnumerator インターフェイスの Reset メソッドは、テキスト ファイルの先頭を指すよう反復子に指示し、現在の項目の値をクリアします。 Reset メソッドに次のコードを追加します。

    Public Sub Reset() _
        Implements System.Collections.IEnumerator.Reset
    
        _sr.DiscardBufferedData()
        _sr.BaseStream.Seek(0, IO.SeekOrigin.Begin)
        _current = Nothing
    End Sub
    
  7. IEnumerator インターフェイスの Dispose メソッドは、反復子が破棄される前にすべてのアンマネージ リソースが解放されることを保証します。 StreamReader オブジェクトによって使用されるファイル ハンドルはアンマネージ リソースです。このハンドルは、反復子インスタンスが破棄される前に閉じる必要があります。 Dispose メソッド用に生成されたコードを次のコードに置き換えます。

    Private disposedValue As Boolean = False
    
    Protected Overridable Sub Dispose(ByVal disposing As Boolean)
        If Not Me.disposedValue Then
            If disposing Then
                ' Dispose of managed resources.
            End If
            _current = Nothing
            _sr.Close()
            _sr.Dispose()
        End If
    
        Me.disposedValue = True
    End Sub
    
    Public Sub Dispose() Implements IDisposable.Dispose
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub
    
    Protected Overrides Sub Finalize()
        Dispose(False)
    End Sub
    

サンプル反復子の使用

コードでは、For Next ループや LINQ クエリなどの IEnumerable を実装するオブジェクトを必要とする制御構造と一緒に、列挙可能なクラスを使用できます。 次の例は、LINQ クエリの StreamReaderEnumerable を示しています。

Dim adminRequests =
    From line In New StreamReaderEnumerable("..\..\log.txt")
    Where line.Contains("admin.aspx 401")

Dim results = adminRequests.ToList()

関連項目