演练:在 Visual Basic 中实现 IEnumerable(Of T)

IEnumerable<T> 接口由每次可以返回一项值序列的类实现。 一次返回一项数据的优点是,无需将完整的数据集加载到内存中即可使用它。 只需使用足够的内存来加载数据中的单个项。 实现 IEnumerable(T) 接口的类可用于 For Each 循环或 LINQ 查询。

例如,假设某个应用程序必须读取一个大型文本文件,并从该文件中返回与特定搜索条件相匹配的每一行。 应用程序使用 LINQ 查询从文件返回与指定条件相匹配的行。 若要通过使用 LINQ 查询来查询文件的内容,应用程序可以将文件的内容加载到数组或集合中。 但是,将整个文件加载到数组或集合会占用比所需的更多的内存。 LINQ 查询可以使用可枚举的类(仅返回与搜索条件匹配的值)来查询文件内容。 仅返回几个匹配值的查询将消耗更少的内存。

你可以创建一个实现 IEnumerable<T> 接口的类,以将源数据作为可枚举数据公开。 实现接口 IEnumerable(T) 的类将需要另一个实现 IEnumerator<T> 接口的类来循环访问源数据。 这两个类使你能够按特定类型按顺序返回数据项。

本演练演示如何创建实现 IEnumerable(Of String) 接口的类和实现 IEnumerator(Of String) 接口的类,以一次读取一行文本文件。

注意

以下说明中的某些 Visual Studio 用户界面元素在计算机上出现的名称或位置可能会不同。 这些元素取决于你所使用的 Visual Studio 版本和你所使用的设置。 有关详细信息,请参阅个性化设置 IDE

创建可枚举类

创建可枚举类项目

  1. 在 Visual Basic 中的“文件”菜单上,指向“新建”,然后单击“项目”。

  2. 在“新建项目”对话框的“项目类型”窗格中,确保选中“Windows”。 在“模板”窗格中,选择“类库”。 在“名称”框中,键入 StreamReaderEnumerable,然后单击“确定”。 将显示新项目。

  3. 在“解决方案资源管理器”中,右键单击 Class1.vb 文件,然后单击“重命名”。 将文件重命名为 StreamReaderEnumerable.vb,然后按 Enter。 重命名文件也会将类重命名为 StreamReaderEnumerable。 此类将实现 IEnumerable(Of String) 接口。

  4. 右键单击“StreamReaderEnumerable”项目,指向“添加”,然后单击“新建项”。 选择“类”模板。 在“名称”框中,键入 StreamReaderEnumerator.vb,然后单击“确定” 。

此项目中的第一个类是可枚举类并将实现 IEnumerable(Of String) 接口。 此泛型接口实现 IEnumerable 接口,并保证此类的使用者可以访问类型为 String 的值。

添加代码以实现 IEnumerable

  1. 打开 StreamReaderEnumerable.vb 文件。

  2. Public Class StreamReaderEnumerable 后面的行中,输入以下内容并按 Enter。

    Implements IEnumerable(Of String)
    

    Visual Basic 使用 IEnumerable(Of String) 接口所需的成员自动填充类。

  3. 此可枚举类将一次一行地读取文本文件中的行。 将下面的代码添加到类以公开公共构造函数,该构造函数采用文件路径作为输入参数。

    Private _filePath As String
    
    Public Sub New(ByVal filePath As String)
        _filePath = filePath
    End Sub
    
  4. 接口 IEnumerable(Of String) 的方法 GetEnumerator的实现 将返回类 StreamReaderEnumerator 的新实例。 可以将 IEnumerable 类的方法 GetEnumerator 实现 Private,因为必须仅公开接口 IEnumerable(Of String) 的成员。 将 Visual Basic 为 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)
    

    Visual Basic 使用 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 类的属性 Current 实现 Private,因为必须仅公开接口 IEnumerator(Of String) 的成员。 将 Visual Basic 为 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 对象使用的文件句柄是非托管资源,必须在销毁迭代器实例之前关闭。 将 Visual Basic 为 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
    

使用示例迭代器

你可以在代码中将可枚举的类与需要实现 IEnumerable 的对象(如 For Next 循环或 LINQ 查询)的控制结构一起使用。 以下示例演示了 LINQ 查询中的 StreamReaderEnumerable

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

Dim results = adminRequests.ToList()

另请参阅