Breaking completely from my usual programming topics, which surround C# most of the time, today I will be looking at Some VB.NET, and, more generally, the topic of iterator methods. For quite some time, Visual Basic.NET has been something of the younger brother to C#; it usually got features long after C# did. Iterator methods are no exception.
What is an “Iterator Method” Anyway?
An iterator method is a coroutine. The best way to describe it is to see it in action. Take the following C# Code as an example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public class testprogram { public IEnumerable<int> SomeRange(int minvalue,int maxvalue) { for (int i=minvalue;i<maxvalue;i++) yield return i; } public static void Main(String[] args) { foreach(int value in SomeRange(0,50)) { if(value==25) break; Console.WriteLine(value); } } } |
In this example, the code would output the values 0 through 24. The difference between this and say something like this:
1 2 3 4 5 6 7 8 9 10 |
public IEnumerable<int> SomeRange(int minvalue,int maxvalue) { List<int> createlist = new List<int>(); for(int i=minvalue;i<maxvalue;i++) createlist.Add(i); return createlist; } |
Is one of both semantics as well as performance. In the first case, the iterator routine is only executed until the next yield, or the iterator routine returns. At which point, either the next element will be passed through the foreach body or the foreach loop will be finished. So, for example, the iterator version of SomeRange() will not iterate past 24 in this example, but the latter does, since it constructs the entire list, and that resulting list is then dealt with using the list enumerator.
One particularly useful purpose is for endless sequences; the second method is impossible for this, since you cannot simply fill a list with an infinite sequence. The iterator routine pattern allows you to define a sequence based on a larger code block by yielding specific values to the enumerating routine. Many other languages have support for the concept of iterator functions. Python, for example, has them, and they work very similarly:
1 2 3 4 5 6 7 8 9 10 11 12 |
#!/usr/bin/python def itertest(): x=0 while(True): x=x+1 yield x for x in itertest(): print(x) if x > 50: break |
In that example, itertest() is a “generator” or iterator function, just like the C# Example above. Because Python is a strongly-typed dynamic language, it has very few special things you need to do; basically, all you have to do is use yield instead of return. the C# Example has to use yield return, as well as having the function signature return a IEnumerable. C++ has a concept of iterator classes, which doesn’t make the syntax simple and “language-defined” but provides a well-defined set of abstract classes that can be relied on.
Visual Basic 6
Visual Basic 6 is an interesting case. The language itself is quite limited. Of course it has absolutely no concept of iterators; it’s For…Each loop acts on an IEnumVariant interface, but you cannot even implement this interface yourself easily. Implementing IEnumVariant in VB6 is possible, but it requires a lot of manual hacking of virtual call tables to point to module-level functions, exacerbated by the fact that the IEnumVariant Interface has method names that are Visual Basic 6 reserved words.
Visual Basic .NET
Visual Basic .NET has been a bit better off than VB6 since inception; it doesn’t directly support iterator methods/coroutines yet (it will in VB10) But you can at least implement the IEnumerator interface. You do lose the ability to have the nice iterator syntax that C# and Python have, though.
Faking it
It is, however, possible to sort of fake it with VB.NET, by creating your own implementation of an enumerator that accepts a delegate; that delegate is passed a single argument- the iterator object- which has two methods- Yield, and Break. Yield returns the next value in the sequence, and Break cancels the iteration. Note that the implementation I came up with does not work like the versions in either Python or C#; in both those cases, the compiler/interpreter turns the iterator function into a state machine. My implementation uses a threaded model- the MoveNext() routine waits until Yield or Break is called before returning. Here is the implementation I came up with. Bear in mind I don’t usually work with VB.NET…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
Public Class Iterator(Of T) Implements IEnumerable(Of T) Implements IEnumerator(Of T) Public Delegate Sub IteratorRoutine(ByVal ih As Iterator(Of T)) Private Coroutine As Thread Private CurrentValue As T Private hasNext As Boolean Private IterationCompleted As Boolean Private CalledUs As Boolean 'Yields the given value Public Function Yield(ByVal YieldValue As T) As T Yield = YieldValue Static InCall As Object If InCall Is Nothing Then InCall = New Object() SyncLock (InCall) SyncLock (Me) hasNext = True CurrentValue = YieldValue CalledUs = True End SyncLock End SyncLock InCall = Nothing End Function Public Sub Break() SyncLock (Me) IterationCompleted = True CalledUs = True End SyncLock End Sub Public Sub Dispose() Implements IDisposable.Dispose If Not Coroutine Is Nothing Then Coroutine.Abort() End If End Sub Public Sub Coroutinerunner(ByVal parameter As Object) Try CalledUs = True Do While Not IterationCompleted AndAlso CalledUs CalledUs = False hasNext = False _iterator(Me) If Not CalledUs Then IterationCompleted = True Do SyncLock (Me) If Not hasNext AndAlso Not IterationCompleted Then Exit Do End SyncLock Thread.Sleep(0) Loop Loop Catch exx As ThreadAbortException End Try End Sub Public Function MoveNext() As Boolean Implements IEnumerator.MoveNext CalledUs = False hasNext = False If Coroutine Is Nothing Then 'we need to start the thread. Coroutine = New Thread(AddressOf Coroutinerunner) Coroutine.Start() End If Do SyncLock (Me) If (hasNext OrElse IterationCompleted) Then Exit Do End SyncLock Thread.Sleep(0) Loop If hasNext Then Return True Else Return False End Function Public Sub Reset() Implements IEnumerator.Reset Throw New NotImplementedException() End Sub Public ReadOnly Property IEnumerator_Current() As T Implements IEnumerator(Of T).Current Get Return CurrentValue End Get End Property Public ReadOnly Property Current() As Object Implements IEnumerator.Current Get Return CurrentValue End Get End Property Private _iterator As Iterator(Of T).IteratorRoutine Public Sub New(ByVal iterator As Iterator(Of T).IteratorRoutine) _iterator = iterator End Sub Public Function IEnumerable_GetEnumerator() As IEnumerator(Of T) Implements IEnumerable(Of T).GetEnumerator Return Me End Function Public Function GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator Return Me End Function End Class |
Usage of this class is relatively simple, compared to having to write your own full blown Enumerable implementation. First, you need a method satisfying the delegate:
1 2 3 4 5 6 7 8 9 10 |
Sub testIterator(ByVal ih As Iterator(Of Double)) Dim y As Double For y = 0 To 10 Step 1 If y > 5 Then Thread.Sleep(500) ih.Yield(y) If (y > 8) Then ih.Break() Debug.Print("Returning " & y) Next End Sub |
Note the use of ih.Yield() and ih.Break(), which “emulate” the appropriate statements from C# (Or Python, for that matter). Using it would look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
Sub Main() Dim iterate As Double For Each iterate In New Iterator(Of Double)(AddressOf testIterator) Console.WriteLine(iterate) Next Console.ReadKey() End Sub |
It’s not as succint as the C# version but still a lot shorter.
On the horizon
Thankfully VB10 will remove this goofy problem- it adds support for iterator methods. Though it’s still a bit goofy, it does allow some things C# doesn’t such as anonymous iterator methods.
Have something to say about this post? Comment!