演练:在 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 并按下回车键。 重命名文件也将类重命名为 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 会自动将接口所需的成员填充到类中。

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

    Private _filePath As String
    
    Public Sub New(ByVal filePath As String)
        _filePath = filePath
    End Sub
    
  4. 您对GetEnumerator接口的IEnumerable(Of String)方法的实现将返回一个StreamReaderEnumerator类的新实例。 可以将 GetEnumerator 类的方法 IEnumerable 实现 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 会自动将接口所需的成员填充到类中。

  3. 枚举器类将打开文本文件,并执行文件 I/O 以读取文件中的行。 将以下代码添加到类以公开公共构造函数,该构造函数将文件路径作为输入参数,并打开文本文件进行读取。

    Private _sr As IO.StreamReader
    
    Public Sub New(ByVal filePath As String)
        _sr = New IO.StreamReader(filePath)
    End Sub
    
  4. 接口CurrentIEnumerator(Of String)IEnumerator属性以String的形式从文本文件中返回当前项。 可以将 Current 类的属性 IEnumerator 实现 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. MoveNext 接口的方法 IEnumerator 导航到文本文件中的下一项并更新 Current 属性返回的值。 如果没有要读取的项,该方法 MoveNextFalse返回;否则该方法 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. Reset 接口的 IEnumerator 方法指示迭代器指向文本文件的开头,并清除当前项值。 将以下代码添加到 Reset 方法中。

    Public Sub Reset() _
        Implements System.Collections.IEnumerator.Reset
    
        _sr.DiscardBufferedData()
        _sr.BaseStream.Seek(0, IO.SeekOrigin.Begin)
        _current = Nothing
    End Sub
    
  7. Dispose 接口的 IEnumerator 方法保证在迭代器销毁之前释放所有非托管资源。 迭代器实例使用的 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()

另请参阅