选择你的枚举器和我的。理解 Yield 和 IEnumerable (VB)
这是“选择你的枚举器和我的。理解 Yield 和 IEnumerable (C#)”的替代版本
引言
这是 C# 文章的 VB 版本,我决定代码和文本之间存在足够大的差异,值得将其作为其他文章的替代版本发布,因为我认为如果尝试合并这两者,文章会太长而难以理解。
我正在开发一个未完成的线性代数(矩阵)类,并希望能够按行(水平)或按列(垂直)进行枚举。因此,我尝试实现 IEnumerable
,这需要实现一个名为 GetEnumerator()
的方法。然后事情可能会变得有些混乱。
- 创建一个实现
IEnumerator
的第二个类,并从GetEnumerator()
方法返回该类的新实例并不难。- 创建多个类也并非难事(垂直和水平),并使用属性在它们之间进行选择也很简单。
- 但是
Yield
呢?确切地说,如何使用它?它“更好”吗?我应该在矩阵类中使用它,还是在IEnumerator
类中使用它? - 能否使用
Yield
,同时仍然有多种枚举方式?
我倾向于发现关于这个主题(以及许多其他主题)的文章一开始都很简单,然后变得比我想要的更复杂/理论化/详细。我更喜欢基本、实用的建议和指导,辅以少量的理论和解释。我可以一点一点地学习,而不是一下子全部学完。我不是谢尔顿。事实上,在我完成附加演示应用程序的编写之前,我并不完全确定,那时我(从实践角度)就明白了。因此,我们将探讨如何实现 IEnumerable
,通过创建 IEnumerator
类或使用 Yield
——我们将比较这两种方法。我还会提及至少一种让类可枚举的其他方法(无需显式实现 IEnumerable
)。我们还将看到两种方法,允许类的用户从 n
种不同的枚举方式中进行选择(你实现的数量都可以),一种使用 Yield
,另一种使用 IEnumerators
。
如果你只是想了解(和我一样)Yield
是否是创建 IEnumerator
类的替代方法(而不是互补的方法,例如),那么答案是:是的;尽管存在差异,在许多情况下可能并非微不足道,我们将对此进行简要回顾。
什么是 Yield?
如果你是来这里试图弄清楚 Yield
是什么/做什么的,那么这是我的看法。
当你使用带有 Yield
的 Iterator
方法时,它不会执行到自然结束,每次遇到 Yield
时,它都会执行与 Return
语句相同的操作,然后停在原地,方法不会结束,下次调用该方法时,它会从上次中断的地方继续,即每次调用它时都不会重新开始。因此,在 For Each
循环中,这些方法只会从头到尾运行一次,而不是为集合、数组或其他数据分组中的每个值运行一次。它是一个多次返回值的函数,而不是一次——魔法(或者也许不是魔法,只是编译器为我们节省了时间,因为 幕后(^) 它似乎会创建类似 IEnumerator
的代码……)
它是为谁准备的?
- 初学者。
- 任何以前没有实现过
IEnumerable
(通过IEnumerator
或Yield
)并想要示例的人。 - 任何想要为
IEnumerable
类实现多个枚举器的人。 - 任何还没有掌握网上百万种其他解释中的一种的人;这只是我在这堆解释中的补充,我希望你能比以前读过的其他解释更容易消化,如果不行,那也没关系,你还有 999,999 种选择(尽管它们通常是 C# 而不是 VB)!
快速摘要
- 我们可以使用
Yield
作为替代方法,在实现IEnumerable
(特别是GetEnumerator()
方法)时创建IEnumerator
类。 - 在某些情况下,特别是中等到高复杂度的情况下,
Yield
比IEnumerator
类更容易编写、理解和阅读。 - 一个
IEnumerable
类可以有多个枚举器,用户可以在运行时选择。 - 你可以通过实现一个返回
IEnumerable
类型的方法来使一个类可枚举,而无需将其声明为IEnumerable
。- 实现多个返回
IEnumerable
类型的方法与第 3 点效果相同。
- 实现多个返回
- 当你使用
Yield
时,编译器会创建一个IEnumerator
类,所以从某些方面来说,它并没有什么不同,但你编写的代码可能更容易阅读(代码的效率是另一个问题),请参见最后一节。
警告 1:如果你喜欢你的模式和原则,那么即使在这段简短的代码中,我也毫无疑问地打破了许多。生活就是这样!
警告 2:如果语义上缺乏细节,那么我很抱歉。我从实践角度写作,有时为了保持简单、实用和可读性,我会有意(或无意地)省略细节。因此,两个人对缺失细节的解释可能不同。如果你需要或想绝对准确地获取这些细节,那么有大量此类信息可用,我在这里不会试图复制它,这不是我的意图。如果存在明显的书写错误,我非常乐意纠正。
目录
背景
本文的起源是(是一个)一个未完成的线性代数(矩阵)项目。我希望能够通过列(垂直)或行(水平)遍历矩阵,这意味着需要实现 IEnumerable
。好的,那么我们该怎么做呢?嗯,我们从经典的 Google 搜索开始。经过大量令人沮丧的时间后,我有一个好想法,我可以使用 Yield
关键字,或者创建一个实现 IEnumerator
的类,或者可能我必须同时做这两件事,可以两者都做,不必做其中任何一个,或者必须做其他事情;谁知道呢?
最终,你可以选择使用 Yield
或创建一个实现 IEnumerator
的类,这是你的选择。它们可能各自更适合某些事情,我对此了解不足,无法确定。至于我的矩阵类,我还可以实现 2 个枚举器(如果我真的想,甚至可以实现 10 个),每个枚举器做不同的事情,并且可以在运行时进行互换。
枚举的基础知识
实现 IEnumerable
的“经典”(或者如果你喜欢新奇事物,就是“旧”)方法非常简单,所以我将首先进行口头描述,然后添加代码。
IEnumerable
接口仅要求你实现一个返回实现了 IEnumerator
的对象的 GetEnumerator()
函数。然后 IEnumerator
要求你实现几个方法,其中两个关键的方法是 MoveNext()
函数和 Current
属性。
所以,假设你对已实现 IEnumerable
的对象使用 For Each
循环,例如这样
For Each element as Double in EnumerableMatrix
Console.WriteLine(element.ToString())
Next
实际上发生的是,在 EnumerableMatrix
对象上调用了 GetEnumerator()
函数。这会返回一个新对象,我们知道它必须实现了 IEnumerator
。然后可以使用 IEnumerator
的 MoveNext()
函数和 Current
属性来循环遍历这个 IEnumerator
对象。事实上,上面的代码就是这种写法的简写。
Dim ObjectToEnumerate As IEnumerator = EnumerableMatrix.GetEnumerator()
While ObjectToEnumerate.MoveNext() = True
Console.WriteLine(ObjectToEnumerate.Current.ToString())
End While
你可以手动这样做,以获得比 For Each
提供的更多对枚举的控制。在 兴趣点 部分,我们简要查看了 IL 代码,可以直接看到 For Each
被转换成了 MoveNext()
和 Current
,如上所示。
要理解枚举,我们必须先数到十
实现 IEnumerable
所以,如果我们理解了 For Each
循环实际上做了什么,那么理解让对象可枚举的“旧”或经典方法就更简单了。我们从我们想要枚举的类开始。
Public Function GetEnumerator() As IEnumerator(Of Double) _
Implements IEnumerable(Of Double).GetEnumerator
Return New MyNewEnumerator()
End Function
Public Function GetEnumerator1() As IEnumerator Implements Collections.IEnumerable.GetEnumerator
Return Me.GetEnumerator()
End Function
它真的就是这么简单。IEnumerable
只要求实现这两个方法,你只需将不太具体的一个指向另一个。(当然,我们仍然需要编写枚举器类)。
在我的例子中,我正在枚举一个二维 double
数组,并且希望能够在两个不同的 IEnumerators
之间进行选择。所以我们需要做一些更改。我们已经完成了以下工作:
添加了一个构造函数,它只接受一个现有的二维 double
数组。
Public Sub New(matrix As Double(,))
Me._matrix = matrix
Me._matrixEnumerator = Demos.MatrixEnumerator.Horizontal
End Sub
一个属性,允许在不同的枚举方法之间进行选择,以及一个 enum
来表示不同的可能枚举器。
Public Property Enumerator As MatrixEnumerator
Get
Return Me._matrixEnumerator
End Get
Set(value As MatrixEnumerator)
Me._matrixEnumerator = value
End Set
End Property
Public Enum MatrixEnumerator
Vertical
Horizontal
End Enum
最后,一些条件代码来创建所需的 IEnumerator
实例。
Public Function GetEnumerator() As IEnumerator(Of Double) _
Implements IEnumerable(Of Double).GetEnumerator
Select Case Me._matrixEnumerator
Case MatrixEnumerator.Horizontal
Return New HorizontalMatrixEnumerator(Me._matrix)
Case MatrixEnumerator.Vertical
Return New VerticalMatrixEnumerator(Me._matrix)
Case Else
Throw New InvalidOperationException
End Select
End Function
现在我们只需要实现上面代码中提到的那两个新类,即水平和垂直枚举器。
实现 IEnumerator
你可以看到,我们返回的 IEnumerator
取决于类属性 Enumerator
。现在我们只需要创建上面代码中提到的两个 IEnumerator
类。我们将先展示水平枚举器,你将看到我们只需在前面提到的四个方法中添加代码。有各种私有字段来跟踪我们在数组中的位置,除此之外,重要的就是 MoveNext()
方法。你还可以看到我们将二维数组的副本从 IEnumerable
类传递到 IEnumerator
类(我假设这就是为什么在枚举时修改对象通常会导致混乱的原因)。
首先是 private
字段和构造函数。
Public Class HorizontalMatrixEnumerator
Implements IEnumerator(Of Double)
Private _matrix As Double(,)
Private _colIndex As Integer
Private _rowIndex As Integer
Private _curItem As Double
Private _lastCol As Integer
Private _lastRow As Integer
Public Sub New(matrix As Double(,))
Me._matrix = matrix
Me._colIndex = -1
Me._rowIndex = 0
Me._curItem = Nothing
Me._lastCol = matrix.GetUpperBound(1)
Me._lastRow = matrix.GetUpperBound(0)
End Sub
_matrix
字段很明显,它是从IEnumerator
对象发送过来的双精度浮点数二维数组的副本(我特意避免讨论浅拷贝/深拷贝以及值/引用——保持焦点)。_colIndex
和_rowIndex
字段用于跟踪我们在数组中的位置,我们的位置。我们将在稍后解释为什么它们被初始化为0
和-1
。_curItem
是Current
属性背后的字段。_lastCol
和_lastRow
只是方便访问数组的UpperBounds
。
现在是 Current
属性。
Public ReadOnly Property Current As Double Implements IEnumerator(Of Double).Current
Get
If IsNothing(Me._curItem) Then
Throw New InvalidOperationException()
End If
Return Me._curItem
End Get
End Property
如前所述,它只是 _curItem
字段的一个包装器。如果它是 Nothing
,它会抛出异常——它在构造函数中初始化为这样——这确保了如果对象的用户(例如 For Each
循环)在调用 MoveNext()
之前尝试使用它,我们会抛出异常,这比让用户发现代码因为期望一个有用的值却得到了一个它不知道如何处理的东西而崩溃要好一些。
现在是重点,MoveNext()
。
Public Function MoveNext() As Boolean Implements IEnumerator.MoveNext
If Me._colIndex = Me._lastCol And Me._rowIndex = Me._lastRow Then
Return False
End If
If Me._colIndex = Me._lastCol Then
Me._colIndex = 0
Me._rowIndex += 1
Else
Me._colIndex += 1
End If
Me._curItem = Me._matrix(Me._rowIndex, Me._colIndex)
Return True
End Function
- 首先,我们检查我们是否已经到达数组的末尾,如果到达了,那么我们已经完成了,并且返回
False
,允许前面的While True ... End While
循环正常退出。 - 然后我们检查我们是否在行的最后一个列,如果是,我们将列索引重置为
0
,然后移动到下一行。 - 如果不在行的末尾,那么我们只需移动到该行的下一列。
- 在后两种情况下,我们返回
True
,告诉While True ... End While
循环至少还有一个新值可以遍历。 - 现在是之前承诺的解释,为什么我们将
_rowIndex
初始化为0
而_colIndex
初始化为-1
?好吧,MoveNext()
在Current
之前被调用,所以如果我们都设置为0
,那么上面的代码将立即将我们移动到第一行的第二列(0,1),我们永远不会将Current
的值设置为 (0,0) 处的数组值。
值得注意的是,上面提供的 MoveNext()
方法本质上是一对嵌套的 for
循环,它们被不必要地复杂化了,yield
版本(我们稍后将展示)更容易编码,也更容易理解。下面的图片应该能让你清楚。
你可以看到,每次列索引达到 2
时,我们需要将其重置为 0
并给行索引加 1
,或者只是给列索引加 1
,直到它达到 2
。
最后,Reset()
方法只是将相关字段恢复到对象创建时的状态。
Public Sub Reset() Implements IEnumerator.Reset
Me._colIndex = -1
Me._rowIndex = 0
Me._curItem = Nothing
End Sub
End Class
垂直枚举器改变了一些东西,但主要是 MoveNext()
方法,如下所示。我们只是在 If ... Then ... Else ...
部分交换了行和列变量,这样我们就可以沿着列向下移动而不是跨行移动。我们还初始化 _rowIndex
为 -1
(而不是 0
),_colIndex
为 0
(而不是 -1
)。
Public Function MoveNext() As Boolean Implements IEnumerator.MoveNext
If Me._colIndex = Me._lastCol And Me._rowIndex = Me._lastRow Then
Return False
End If
If Me._rowIndex = Me._lastRow Then
Me._rowIndex = 0
Me._colIndex += 1
Else
Me._rowIndex += 1
End If
Me._curItem = Me._matrix(Me._rowIndex, Me._colIndex)
Return True
End Function
虽然这很简单,但这需要创建两个额外的类,并且(相对而言)需要更多的代码,对于如此简单的事情来说似乎有点多余,我们能做得更好吗?让我们尝试使用 yield……
谁需要 IEnumerators?
屈服于诱惑。它可能不会再出现在你面前。
那么,我们如何使用 Yield
来做同样的事情呢?大部分代码没有改变。第一件事是 GetEnumerator()
方法不创建新对象,它调用几个 private
类方法。除此之外,它完全相同,你必须仔细观察才能注意到一些变化。
Public Function GetEnumerator() As IEnumerator(Of Double) _
Implements IEnumerable(Of Double).GetEnumerator
Select Case Me._matrixEnumerator
Case Demos.MatrixEnumerator.Horizontal
Return Me.HorizontalEnumerator()
Case Demos.MatrixEnumerator.Vertical
Return Me.VerticalEnumerator()
Case Else
Throw New InvalidOperationException
End Select
End Function
基本上相同,但不是 Return New HorizontalMatrixEnumerator(Me._matrix)
,而是看到 Return Me.HorizontalEnumerator()
,这是一个方法引用而不是一个对象。然后,我们只需要实现那两个新方法。
Private Iterator Function VerticalEnumerator() As IEnumerator(Of Double)
If IsNothing(Me._matrix) Then
Throw New InvalidOperationException()
End If
For Col As Integer = 0 To Me._matrix.GetUpperBound(1)
For Row As Integer = 0 To Me._matrix.GetUpperBound(0)
Yield Me._matrix(Row, Col)
Next
Next
End Function
Private Iterator Function HorizontalEnumerator() As IEnumerable(Of Double)
If IsNothing(Me._matrix) Then
Throw New InvalidOperationException()
End If
For Row As Integer = 0 To Me._matrix.GetUpperBound(0)
For Col As Integer = 0 To Me._matrix.GetUpperBound(1)
Yield Me._matrix(Row, Col)
Next
Next
End Function
我花了一段时间才弄清楚如何声明这两个方法,但最终你发现它们需要是 Private Iterator Function
,返回类型为 IEnumerator(Of Double)
。你可以在代码中看到这一点,除此之外,它 no 涉及比遍历数组更多的东西。技术上没有更容易,实际上根本没有更容易,但肯定代码更少,而且没有那些额外的类需要维护。
清楚的是,这比两个 IEnumerator
类少了很多代码。它也更容易看懂,很明显是两个嵌套的 For
循环,正如我们上面所说,这正是 MoveNext()
方法实际上正在做的事情,只是方式更复杂。
作为解释,这真的不多,但如果你已经阅读并理解了如何用 IEnumerators
做同样的事情,那么理解 Yield
就很简单了。
- 认识到代码看起来与
MoveNext()
方法(本质上)非常相似,只是它更简单。 - 认识到当你使用带有
Yield
的Iterator
方法时,它不会执行到自然结束,每次遇到Yield
时,它都会执行与Return
语句相同的操作,然后停在原地,方法不会结束,下次调用该方法时,它会从上次中断的地方继续,即每次调用它时都不会重新开始。因此,在For Each
循环中,此方法只会从头到尾运行一次,而不是为数组中的每个值运行一次。它是一个多次返回值而不是一次的函数——魔法(或者也许不是魔法,只是编译器为我们节省了时间,因为 幕后(^) 它似乎会创建类似IEnumerator
的代码……)
两者之间的唯一区别是遍历数组的方式,是先遍历列还是先遍历行。
状态机
如果这个解释对你不起作用,还有其他选项可以从不同角度和更详细地解释它,Google(^) 是你的朋友。
好的,我不会尝试详细解释(这篇文章是针对初学者的,而且我还有很多不了解的地方,或者至少我认为是这样),只解释足够(我希望)让你明白 Yield
实际上做什么以及如何做。你可以几乎把 Iterator
方法看作一个单独的程序,在你的 IEnumerable
对象上运行 For Each
循环的程序会向另一个程序发送一条消息说:“给我下一个”——现在这听起来就像一个 MoveNext()
后跟一个 Current
——但巧妙之处在于,另一个程序在执行完 Yield
语句后会停止执行,然后等待你要求下一个,这时它会执行下一条语句并继续执行,直到达到下一个 Yield
语句,执行它,然后再次等待。在上面的例子中,每次它达到“下一个”Yield
语句时,它实际上是同一个,但在一个循环中,你也可以将一组 Yield
语句一个接一个地放在一起(参见 MSDN 示例(^))。
关注点
你学到了什么有趣/好玩/烦人的东西吗?
是的,某个笨蛋(在设计 .NET 和 VB / C# 时)决定数组索引为(行, 列),而 DataGridView
的索引为(列, 行),这很明智。
For Each = While MoveNext() + Current
我们可以清楚地展示之前提到的观点,即在 IEnumerable
对象上运行 foreach 等同于手动创建 IEnumerator
并启动一个 While True ... End While
循环。这恰好是针对使用 IEnumerator
而非 Yield
的对象执行的 For Each
的代码,尽管对于 Yield
版本来说非常相似。
IL_0169: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`1<float64> _
MadBadger.Demos.ByEnumerator::GetEnumerator()
IL_016e: stloc.s VB$t_ref$L1
IL_0170: br.s IL_019b
// loop start (head: IL_019b)
IL_0172: ldloc.s VB$t_ref$L1
IL_0174: callvirt instance !0 class _
[mscorlib]System.Collections.Generic.IEnumerator`1<float64>::get_Current()
IL_0179: stloc.s Val
IL_017b: ldloc.s output
IL_017d: ldloc.s Val
IL_017f: ldc.i4.2
IL_0180: call float64 [mscorlib]System.Math::Round(float64, int32)
IL_0185: stloc.s VB$t_double$S0
IL_0187: ldloca.s VB$t_double$S0
IL_0189: call instance string [mscorlib]System.Double::ToString()
IL_018e: ldstr " ¦ "
IL_0193: call string [mscorlib]System.String::Concat(string, string, string)
IL_0198: stloc.s output
IL_019a: nop
IL_019b: ldloc.s VB$t_ref$L1
IL_019d: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
IL_01a2: stloc.s VB$CG$t_bool$S0
IL_01a4: ldloc.s VB$CG$t_bool$S0
IL_01a6: brtrue.s IL_0172
// end loop
IL_01a8: nop
IL_01a9: leave.s IL_01c3
} // end .try
finally
{
IL_01ab: ldloc.s VB$t_ref$L1
IL_01ad: ldnull
IL_01ae: ceq
IL_01b0: ldc.i4.0
IL_01b1: ceq
IL_01b3: stloc.s VB$CG$t_bool$S0
IL_01b5: ldloc.s VB$CG$t_bool$S0
IL_01b7: brfalse.s IL_01c1
IL_01b9: ldloc.s VB$t_ref$L1
IL_01bb: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_01c0: nop
IL_01c1: nop
IL_01c2: endfinally
} // end handler
你可以清楚地看到,在 IL_0170
处,它(无条件地)跳转到包含调用 MoveNext()
的短片段,从 IL_019b
开始。在该片段的末尾,它以 true
(IL_01a6
) 为条件分支到上面的稍长片段(假设 MoveNext()
返回 True
),从 IL_0172
开始,然后调用 Current
。这完全符合 While True) ... End While
循环,即如果可枚举对象为空,它永远不会尝试调用 Current
;同时对于非空对象,MoveNext()
第一次返回 False
时,它会尝试移动到最后一个位置之后,此时,以 True
(IL_01a6
) 为条件的跳转不会跳转,代码会继续执行出循环。
编译器如何实现 Yield?
我以为我记得读过,当你使用 yield 时,编译器会创建 IEnumerator
来响应,所以它基本上只是一个允许更易于阅读、编写和维护的代码的简写,我想看看这个是否是真的——因为我有一个完美的例子来测试它——通过查看编译器生成的 IL 代码。(尝试 ILSpy(^) 或随附的 ILDisassembler
Windows 7.1 SDK(^))。这是你看到的结果,首先我们来看看类和生成的代码,特别是 ByYield
类。
这是我们高亮的 HorizontalEnumerator
方法,那么 IL 代码说了什么?
.method private
instance class [mscorlib]System.Collections.Generic.IEnumerable`1<float64>
HorizontalEnumerator () cil managed
{
.custom instance void
[mscorlib]System.Runtime.CompilerServices.IteratorStateMachineAttribute::.ctor
(class [mscorlib]System.Type) = (
01 00 3e 4d 61 64 42 61 64 67 65 72 2e 44 65 6d
6f 73 2e 42 79 59 69 65 6c 64 2b 56 42 24 53 74
61 74 65 4d 61 63 68 69 6e 65 5f 31 5f 48 6f 72
69 7a 6f 6e 74 61 6c 45 6e 75 6d 65 72 61 74 6f
72 00 00
)
.custom instance void [mscorlib]System.Diagnostics.DebuggerStepThroughAttribute::.ctor() = (
01 00 00 00
)
// Method begins at RVA 0x2ae4
// Code size 27 (0x1b)
.maxstack 2
.locals init (
[0] class MadBadger.Demos.ByYield/VB$StateMachine_1_HorizontalEnumerator $sm,
[1] class [mscorlib]System.Collections.Generic.IEnumerable`1<float64> HorizontalEnumerator
)
IL_0000: newobj instance
void MadBadger.Demos.ByYield/VB$StateMachine_1_HorizontalEnumerator::.ctor()
IL_0005: stloc.0
IL_0006: ldloc.0
IL_0007: ldarg.0
IL_0008: stfld class MadBadger.Demos.ByYield
MadBadger.Demos.ByYield/VB$StateMachine_1_HorizontalEnumerator::$VB$Me
IL_000d: ldloc.0
IL_000e: ldc.i4.s -2
IL_0010: stfld int32 MadBadger.Demos.ByYield/VB$StateMachine_1_HorizontalEnumerator::$State
IL_0015: ldloc.0
IL_0016: stloc.1
IL_0017: br.s IL_0019
IL_0019: ldloc.1
IL_001a: ret
} // end of method ByYield::HorizontalEnumerator
我不完全理解所有内容,但我确实看到了对 ByYield/VB$StateMachine_1_HorizontalEnumerator::$VB$Me
的引用。我们可以在上面的图片中看到——它是 ByYield
描述中的第四项,就在 DerivedTypes
下面。这是该类成员包含的内容。
我们老朋友,MoveNext()
、 Reset()
和 Current
属性;这些不是在 ByYield
类中编写的,编译器已经创建了它们。显然,编译器将 yield
视为“请为我写一个 IEnumerator
”的简写。
我们可以(非常业余地)比较一下,编译器生成的 IEnumerator
是否比它尝试优化我们编写的 MoveNext()
的版本好很多。左边是编译器将带有 Yield
的两个嵌套 For
循环转换过来的代码。右边是它转换我 MoveNext()
方法的代码。
.method assembly hidebysig strict virtual
instance bool MoveNext () cil managed
{
.custom instance void _
[mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
01 00 00 00
)
.override method instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
// Method begins at RVA 0x5830
// Code size 325 (0x145)
.maxstack 5
.locals init (
[0] bool $VB$ResumableLocal_Col$1,
[1] bool VB$doFinallyBodies,
[2] class [mscorlib]System.Exception $ex,
[3] bool VB$CG$t_bool$S0,
[4] int32 VB$CG$t_i4$S0
)
IL_0000: ldc.i4.1
IL_0001: stloc.1
IL_0002: nop
.try
{
IL_0003: ldarg.0
IL_0004: ldfld int32 MadBadger.Demos.ByYield/VB$StateMachine_1_HorizontalEnumerator::$State
IL_0009: switch (IL_00bc, IL_0021)
IL_0016: ldarg.0
IL_0017: ldfld bool
MadBadger.Demos.ByYield/VB$StateMachine_1_HorizontalEnumerator::$Disposing
IL_001c: stloc.3
IL_001d: ldloc.3
IL_001e: brfalse.s IL_0029
IL_0020: nop
IL_0021: nop
IL_0022: ldc.i4.0
IL_0023: stloc.0
IL_0024: leave IL_0143
IL_0029: nop
IL_002a: nop
IL_002b: ldarg.0
IL_002c: ldfld class
MadBadger.Demos.ByYield
MadBadger.Demos.ByYield/VB$StateMachine_1_HorizontalEnumerator::$VB$Me
IL_0031: ldfld float64[0..., 0...] MadBadger.Demos.ByYield::_matrix
IL_0036: call bool
[Microsoft.VisualBasic]Microsoft.VisualBasic.Information::IsNothing(object)
IL_003b: stloc.3
IL_003c: ldloc.3
IL_003d: brfalse.s IL_0045
IL_003f: newobj instance void [mscorlib]System.InvalidOperationException::.ctor()
IL_0044: throw
IL_0045: nop
IL_0046: ldarg.0
IL_0047: ldc.i4.0
IL_0048: ldarg.0
IL_0049: ldarg.0
IL_004a: ldfld class
MadBadger.Demos.ByYield MadBadger.Demos.ByYield/VB$StateMachine_1_HorizontalEnumerator::$VB$Me
IL_004f: ldfld float64[0..., 0...] MadBadger.Demos.ByYield::_matrix
IL_0054: ldc.i4.0
IL_0055: callvirt instance int32
[mscorlib]System.Array::GetUpperBound(int32)
IL_005a: stfld int32
MadBadger.Demos.ByYield/VB$StateMachine_1_HorizontalEnumerator::$VB$ResumableLocal_VB$t_i4$L0$1
IL_005f: stfld int32
MadBadger.Demos.ByYield/VB$StateMachine_1_HorizontalEnumerator::$VB$ResumableLocal_Row$1
IL_0064: br IL_0103
IL_0069: ldarg.0
IL_006a: ldc.i4.0
IL_006b: ldarg.0
IL_006c: ldarg.0
IL_006d: ldfld class MadBadger.Demos.ByYield
MadBadger.Demos.ByYield/VB$StateMachine_1_HorizontalEnumerator::$VB$Me
IL_0072: ldfld float64[0..., 0...] MadBadger.Demos.ByYield::_matrix
IL_0077: ldc.i4.1
IL_0078: callvirt instance int32 [mscorlib]System.Array::GetUpperBound(int32)
IL_007d: stfld int32
MadBadger.Demos.ByYield/VB$StateMachine_1_HorizontalEnumerator::$VB$ResumableLocal_VB$t_i4$L1$1
IL_0082: stfld int32
MadBadger.Demos.ByYield/VB$StateMachine_1_HorizontalEnumerator::$VB$ResumableLocal_Col$1
IL_0087: br.s IL_00e2
IL_0089: ldarg.0
IL_008a: ldarg.0
IL_008b: ldfld class
MadBadger.Demos.ByYield MadBadger.Demos.ByYield/VB$StateMachine_1_HorizontalEnumerator::$VB$Me
IL_0090: ldfld float64[0..., 0...] MadBadger.Demos.ByYield::_matrix
IL_0095: ldarg.0
IL_0096: ldfld int32 _
MadBadger.Demos.ByYield/VB$StateMachine_1_HorizontalEnumerator::$VB$ResumableLocal_Row$1
IL_009b: ldarg.0
IL_009c: ldfld int32 _
MadBadger.Demos.ByYield/VB$StateMachine_1_HorizontalEnumerator::$VB$ResumableLocal_Col$1
IL_00a1: callvirt instance float64 float64[0..., 0...]::Get(int32, int32)
IL_00a6: stfld float64 MadBadger.Demos.ByYield/VB$StateMachine_1_HorizontalEnumerator::$Current
IL_00ab: ldarg.0
IL_00ac: ldc.i4.0
IL_00ad: stfld int32 MadBadger.Demos.ByYield/VB$StateMachine_1_HorizontalEnumerator::$State
IL_00b2: ldc.i4.0
IL_00b3: stloc.1
IL_00b4: ldc.i4.1
IL_00b5: stloc.0
IL_00b6: leave IL_0143
IL_00bb: nop
IL_00bc: nop
IL_00bd: ldarg.0
IL_00be: ldfld bool MadBadger.Demos.ByYield/VB$StateMachine_1_HorizontalEnumerator::$Disposing
IL_00c3: stloc.3
IL_00c4: ldloc.3
IL_00c5: brfalse.s IL_00cb
IL_00c7: ldc.i4.0
IL_00c8: stloc.0
IL_00c9: leave.s IL_0143
IL_00cb: nop
IL_00cc: ldarg.0
IL_00cd: ldc.i4.m1
IL_00ce: stfld int32 MadBadger.Demos.ByYield/VB$StateMachine_1_HorizontalEnumerator::$State
IL_00d3: nop
IL_00d4: ldarg.0
IL_00d5: dup
IL_00d6: ldfld int32
MadBadger.Demos.ByYield/VB$StateMachine_1_HorizontalEnumerator::$VB$ResumableLocal_Col$1
IL_00db: ldc.i4.1
IL_00dc: add.ovf
IL_00dd: stfld int32
MadBadger.Demos.ByYield/VB$StateMachine_1_HorizontalEnumerator::$VB$ResumableLocal_Col$1
IL_00e2: ldarg.0
IL_00e3: ldfld int32
MadBadger.Demos.ByYield/VB$StateMachine_1_HorizontalEnumerator::$VB$ResumableLocal_Col$1
IL_00e8: ldarg.0
IL_00e9: ldfld int32
MadBadger.Demos.ByYield/VB$StateMachine_1_HorizontalEnumerator::$VB$ResumableLocal_VB$t_i4$L1$1
IL_00ee: stloc.s VB$CG$t_i4$S0
IL_00f0: ldloc.s VB$CG$t_i4$S0
IL_00f2: ble.s IL_0089
IL_00f4: nop
IL_00f5: ldarg.0
IL_00f6: dup
IL_00f7: ldfld int32
MadBadger.Demos.ByYield/VB$StateMachine_1_HorizontalEnumerator::$VB$ResumableLocal_Row$1
IL_00fc: ldc.i4.1
IL_00fd: add.ovf
IL_00fe: stfld int32
MadBadger.Demos.ByYield/VB$StateMachine_1_HorizontalEnumerator::$VB$ResumableLocal_Row$1
IL_0103: ldarg.0
IL_0104: ldfld int32
MadBadger.Demos.ByYield/VB$StateMachine_1_HorizontalEnumerator::$VB$ResumableLocal_Row$1
IL_0109: ldarg.0
IL_010a: ldfld int32
MadBadger.Demos.ByYield/VB$StateMachine_1_HorizontalEnumerator::$VB$ResumableLocal_VB$t_i4$L0$1
IL_010f: stloc.s VB$CG$t_i4$S0
IL_0111: ldloc.s VB$CG$t_i4$S0
IL_0113: ble IL_0069
IL_0118: nop
IL_0119: leave.s IL_0137
IL_011b: leave.s IL_0135
} // end .try
catch [mscorlib]System.Exception
{
IL_011d: dup
IL_011e: call void _
[Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.ProjectData::SetProjectError_
(class [mscorlib]System.Exception)
IL_0123: stloc.2
IL_0124: nop
IL_0125: ldarg.0
IL_0126: ldc.i4.1
IL_0127: stfld int32 MadBadger.Demos.ByYield/VB$StateMachine_1_HorizontalEnumerator::$State
IL_012c: rethrow
IL_012e: call void
[Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.ProjectData::ClearProjectError()
IL_0133: leave.s IL_0135
} // end handler
IL_0135: nop
IL_0136: nop
IL_0137: nop
IL_0138: ldarg.0
IL_0139: ldc.i4.1
IL_013a: stfld int32 MadBadger.Demos.ByYield/VB$StateMachine_1_HorizontalEnumerator::$State
IL_013f: ldc.i4.0
IL_0140: stloc.0
IL_0141: br.s IL_0143
IL_0143: ldloc.0
IL_0144: ret
} // end of method VB$StateMachine_1_HorizontalEnumerator::MoveNext
.method public final newslot strict virtual
instance bool MoveNext () cil managed
{
.override method instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
// Method begins at RVA 0x2268
// Code size 131 (0x83)
.maxstack 4
.locals init (
[0] bool MoveNext,
[1] bool VB$CG$t_bool$S0
)
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldfld int32 MadBadger.Demos.HorizontalMatrixEnumerator::_colIndex
IL_0007: ldarg.0
IL_0008: ldfld int32 MadBadger.Demos.HorizontalMatrixEnumerator::_lastCol
IL_000d: ceq
IL_000f: ldarg.0
IL_0010: ldfld int32 MadBadger.Demos.HorizontalMatrixEnumerator::_rowIndex
IL_0015: ldarg.0
IL_0016: ldfld int32 MadBadger.Demos.HorizontalMatrixEnumerator::_lastRow
IL_001b: ceq
IL_001d: and
IL_001e: stloc.1
IL_001f: ldloc.1
IL_0020: brfalse.s IL_0026
IL_0022: ldc.i4.0
IL_0023: stloc.0
IL_0024: br.s IL_0081
IL_0026: nop
IL_0027: ldarg.0
IL_0028: ldfld int32 MadBadger.Demos.HorizontalMatrixEnumerator::_colIndex
IL_002d: ldarg.0
IL_002e: ldfld int32 MadBadger.Demos.HorizontalMatrixEnumerator::_lastCol
IL_0033: ceq
IL_0035: stloc.1
IL_0036: ldloc.1
IL_0037: brfalse.s IL_0050
IL_0039: ldarg.0
IL_003a: ldc.i4.0
IL_003b: stfld int32 MadBadger.Demos.HorizontalMatrixEnumerator::_colIndex
IL_0040: ldarg.0
IL_0041: ldarg.0
IL_0042: ldfld int32 MadBadger.Demos.HorizontalMatrixEnumerator::_rowIndex
IL_0047: ldc.i4.1
IL_0048: add.ovf
IL_0049: stfld int32 MadBadger.Demos.HorizontalMatrixEnumerator::_rowIndex
IL_004e: br.s IL_005f
IL_0050: nop
IL_0051: ldarg.0
IL_0052: ldarg.0
IL_0053: ldfld int32 MadBadger.Demos.HorizontalMatrixEnumerator::_colIndex
IL_0058: ldc.i4.1
IL_0059: add.ovf
IL_005a: stfld int32 MadBadger.Demos.HorizontalMatrixEnumerator::_colIndex
IL_005f: nop
IL_0060: ldarg.0
IL_0061: ldarg.0
IL_0062: ldfld float64[0..., 0...] MadBadger.Demos.HorizontalMatrixEnumerator::_matrix
IL_0067: ldarg.0
IL_0068: ldfld int32 MadBadger.Demos.HorizontalMatrixEnumerator::_rowIndex
IL_006d: ldarg.0
IL_006e: ldfld int32 MadBadger.Demos.HorizontalMatrixEnumerator::_colIndex
IL_0073: callvirt instance float64 float64[0..., 0...]::Get(int32, int32)
IL_0078: stfld float64 MadBadger.Demos.HorizontalMatrixEnumerator::_curItem
IL_007d: ldc.i4.1
IL_007e: stloc.0
IL_007f: br.s IL_0081
IL_0081: ldloc.0
IL_0082: ret
} // end of method HorizontalMatrixEnumerator::MoveNext
我没有资格专业地评论,但编译器显然需要做更多工作来将两个嵌套的 For
循环通过 Yield
转换成 IEnumerator
,而不是在给定专用 IEnumerator
代码时。(有趣的是,几乎相同的 C# 代码在手动编写的 IEnumerator
和编译器生成的版本之间产生的差异要小得多)。其中哪个更有效率我不知道——也许有人有时间测试……我猜这并不奇怪,编译器能够做到这一点本身就让我印象深刻,尤其当你想象一个带有 Yield
语句的方法可能有多么复杂,而它必须能够处理所有这些。
如果你有一个可能包含大量数据,需要可枚举,并且时间至关重要的类,那么考虑这一点是值得的。在这种情况下,尝试你自己的 IEnumerator
和 Yield
可能是值得花时间的,以查看哪种执行速度更快。
历史
- 版本 1(2013 年 4 月 21 日):从 C# 版本的 v5 编辑而来