演练:在 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。
创建可枚举类
创建可枚举类项目
在 Visual Basic 中的“文件”菜单上,指向“新建”,然后单击“项目”。
在“新建项目”对话框的“项目类型”窗格中,确保选中“Windows”。 在“模板”窗格中,选择“类库”。 在“名称”框中,键入
StreamReaderEnumerable
,然后单击“确定”。 将显示新项目。在“解决方案资源管理器”中,右键单击 Class1.vb 文件,然后单击“重命名”。 将文件重命名为
StreamReaderEnumerable.vb
,然后按 Enter。 重命名文件也会将类重命名为StreamReaderEnumerable
。 此类将实现IEnumerable(Of String)
接口。右键单击“StreamReaderEnumerable”项目,指向“添加”,然后单击“新建项”。 选择“类”模板。 在“名称”框中,键入
StreamReaderEnumerator.vb
,然后单击“确定” 。
此项目中的第一个类是可枚举类并将实现 IEnumerable(Of String)
接口。 此泛型接口实现 IEnumerable 接口,并保证此类的使用者可以访问类型为 String
的值。
添加代码以实现 IEnumerable
打开 StreamReaderEnumerable.vb 文件。
在
Public Class StreamReaderEnumerable
后面的行中,输入以下内容并按 Enter。Implements IEnumerable(Of String)
Visual Basic 使用
IEnumerable(Of String)
接口所需的成员自动填充类。此可枚举类将一次一行地读取文本文件中的行。 将下面的代码添加到类以公开公共构造函数,该构造函数采用文件路径作为输入参数。
Private _filePath As String Public Sub New(ByVal filePath As String) _filePath = filePath End Sub
接口
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
打开 StreamReaderEnumerator.vb 文件。
在
Public Class StreamReaderEnumerator
后面的行中,输入以下内容并按 Enter。Implements IEnumerator(Of String)
Visual Basic 使用
IEnumerator(Of String)
接口所需的成员自动填充类。枚举器类打开文本文件,并执行文件 I/O 以读取文件中的行。 将下面的代码添加到类以公开公共构造函数,该构造函数采用文件路径作为输入参数,并打开文本文件以阅读。
Private _sr As IO.StreamReader Public Sub New(ByVal filePath As String) _sr = New IO.StreamReader(filePath) End Sub
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
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
IEnumerator
接口的Reset
方法指示迭代器指向文本文件的开头,并清除当前项值。 将以下代码添加到Reset
方法中。Public Sub Reset() _ Implements System.Collections.IEnumerator.Reset _sr.DiscardBufferedData() _sr.BaseStream.Seek(0, IO.SeekOrigin.Begin) _current = Nothing End Sub
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()