接口 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
并按下回车键。 重命名文件也将类重命名为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 会自动将接口所需的成员填充到类中。
此枚举类将逐行读取文本文件中的每一行。 将以下代码添加到类,以公开将文件路径作为输入参数的公共构造函数。
Private _filePath As String Public Sub New(ByVal filePath As String) _filePath = filePath End Sub
您对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
打开StreamReaderEnumerator.vb文件。
在后面的
Public Class StreamReaderEnumerator
行中,键入以下内容,然后按 Enter。Implements IEnumerator(Of String)
Visual Basic 会自动将接口所需的成员填充到类中。
枚举器类将打开文本文件,并执行文件 I/O 以读取文件中的行。 将以下代码添加到类以公开公共构造函数,该构造函数将文件路径作为输入参数,并打开文本文件进行读取。
Private _sr As IO.StreamReader Public Sub New(ByVal filePath As String) _sr = New IO.StreamReader(filePath) End Sub
接口
Current
和IEnumerator(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
MoveNext
接口的方法IEnumerator
导航到文本文件中的下一项并更新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
Reset
接口的IEnumerator
方法指示迭代器指向文本文件的开头,并清除当前项值。 将以下代码添加到Reset
方法中。Public Sub Reset() _ Implements System.Collections.IEnumerator.Reset _sr.DiscardBufferedData() _sr.BaseStream.Seek(0, IO.SeekOrigin.Begin) _current = Nothing End Sub
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()