Iteration refers to repeatedly performing some process. Therefore, an iterator is the component that allows this iteration to occur. In development terms, iterators provide a means to iterate over a collection. As discussed in Chapters 7 and 20, this is typically accomplished by using a For Each loop.
Most collections in the .NET framework implement the IEnumerable or IEnumerable(Of T) interface. This interface provides the GetEnumerator method which returns an IEnumerator, which performs the actual iteration for you. Again, all of this is covered in Chapters 7 and 19. The point here is that when you use a For Each loop and specify a collection, the compiler automatically calls the GetEnumerator method implemented by the class.
Providing custom or additional iterators required you to create customized classes that implemented IEnumerator or IEnumerator(Of T). Creating enumerators required a little bit of work because you had to implement several methods that provide the iteration logic.
To help alleviate some of these issues and provide a more concise way to provide custom iterators or control the flow of your code, Microsoft created Iterator and Yield. The Iterator modifier and Yield operator were first introduced in the same CTP add-on that introduced Async and Await. They are now part of version 4.5 of the .NET framework.
It will quickly become apparent why Microsoft released both Async/Await and Iterator/Yield at the same time for both the Visual Studio 2010 update and the .NET 4.5 framework. They actually share much of the same code base and behave in a very similar fashion. Iterator is to Async as Yield is to Await.
The Iterator modifier can be applied to either a function or a Get accessor that returns an IEnumerable, IEnumerbale(Of T), IEnumerator, or IEnumerator(Of T). Once applied, it instructs the compile that the target is an iterator which allows a For Each operation to be performed against it.
Now you need some way to return the data back to the For Each loop that initially called it. This is accomplished using the Yield statement. The Yield operator is always followed by the data to be yielded and looks like this:
Yield "My Data"
Similar to how Await works, once the compiler hits the Yield statement, the yielded value is returned to the calling iterator loop. During the next iteration of the loop, control returns back to the iterator method at the line following the previous Yield.
In order to make the concepts discussed previously make more sense, you will create a new example that demonstrates them. Start by adding the following code to the application (code file: MainWindow.xaml.vb):
Public Sub IteratorBasicExample1() For Each value As String In HelloWorld() TextBoxResult.Text += value + " " Next End Sub Private Iterator Function HelloWorld() As IEnumerable(Of String) Yield "Hello" Yield "World" End Function
The IteratorBasicExample1 method contains a For Each loop that iterators over the HelloWorld method. For Each can be used on this method, because it is marked with the Iterator modifier.
The HelloWorld contains only two Yield statements. The first yields the value “Hello” while the second yields “World.” The way this works is that during the first iteration, “Hello” is returned back to the IteratorBasicExample1 method and written to your text box. During the next iteration, HelloWorld is called again, but this time “World” is returned.
Behind the scenes, the compiler handles a method marked with Iterator in the same fashion that it handles one marked with Async. It creates an anonymous state machine class based on the method. Additional details on this were provided under the Async and Await section of this chapter.
As you should be accustomed to by now, you need to add the example to the list in order to run it. The line to add is:
New With {.Name = "Iterators - The Basics", _ .Lambda = New Action(Sub() IteratorBasicExample1())}
Executing the application should provide you results similar to those in Figure 5.12.
The previous example touched only on the very basics. This example will go a little deeper into the same concepts and cover a few extra points. For this example, you will need the Oceans class. Create this class now by creating a new class file and updating it with the following (code file: Oceans.vb):
Public Class Oceans Implements IEnumerable Dim oceans As List(Of String) = New List(Of String) From {"Pacific", "Atlantic", "Indian", "Southern", "Arctic"} Public ReadOnly Iterator Property WorldOceans As IEnumerable(Of String) Get For Each ocean In oceans Yield ocean Next End Get End Property Public Iterator Function GetEnumerator() As IEnumerator _ Implements IEnumerable.GetEnumerator For Each ocean In oceans Yield ocean Next End Function End Class
This class is a very simple class that just returns a list of oceans stored in an internal collection. The first iterator available is the WorldOceans property. The first new thing you will learn is that Yield statements work just fine inside a loop. The second is that Get accessors work as iterators in the same way as functions do.
The second iterator will be discussed in a moment; for now you need to add the following code to the main application (code file: MainWindow.xaml.vb):
Public Sub IteratorBasicExample2() Dim oceans As Oceans = New Oceans() TextBoxResult.Text = "Oceans from property: " + Environment.NewLine For Each value As String In oceans.WorldOceans TextBoxResult.Text += value + " " Next TextBoxResult.Text += Environment.NewLine TextBoxResult.Text += "Oceans from GetEnumerator: " + Environment.NewLine For Each value As String In oceans TextBoxResult.Text += value + " " Next End Sub
Initially, an instance of your Ocean class is created. The next part of the code iterates over the Ocean.WorldOceans property, writing each returned value to the results text box.
In the second part of the method, you iterate over the object itself. You can do this because the object implements IEnumerable. As mentioned previously, the compiler will automatically call the GetEnumerator method in this situation.
Now look back at the GetEnumerator method that you created in the Ocean class. Since it is marked with the Iterator modifier, the compiler has been kind enough to implement all the underlying IEnumerator methods for you. This saves you the headache of having to do it
Again, you are just writing the returned values to the results text box. However, before you can run the example, you need to add this item to the exampleList collection:
New With {.Name = "Iterators - Oceans example", _ .Lambda = New Action(Sub() IteratorBasicExample2())}
The final results of executing the example are shown in Figure 5.13.
The examples provided in the previous section may not seem very practical because they are focused on the core concepts themselves. They also did not touch on the most powerful feature of iterators, which is the ability to customize the iterator itself.
In order to really see this in action, you are going to create another example. Start by adding the following code to your application (code file: MainWindow.xaml.vb):
Private Iterator Function Primes(max As Integer) As IEnumerable(Of Long) Dim isPrime As Boolean = False For i As Integer = 2 To max isPrime = False ' We know 2 is a prime and we handle it now since ' we are going to rule out all other even numbers If i = 2 Then Yield i Continue For End If ' We don't care about even numbers (except 2) If i Mod 2 = 0 Then Continue For End If isPrime = True For j As Integer = 2 To CLng(Math.Sqrt(i)) ' Check if current value is divisible by something ' other than 1 and itself If i Mod j = 0 Then isPrime = False Exit For End If Next If isPrime Then Yield i Next End Function
You marked the method with the Iterator modifier, so you know you can use For Each over it. You can also see several Yield statements. Basically, a Yield statement will be executed only if the value in question is a prime.
Without the use of iterators you would have had to perform the calculation and store each prime internal, returning a collection at the end. In this case you get each prime as it is discovered.
To use the new iterator, you need to add the following small method to the application (code file: MainWindow.xaml.vb):
Public Sub IteratorAdvancedExample() For Each value As Long In Primes(100) TextBoxResult.Text += value.ToString() + Environment.NewLine Next End Sub
This method iterates over the results return by the Primes method. You provided the number 100 to the method, so all prime numbers between 2 and 100 will be returned.
Add the following item to the example list in order to be able to run it:
New With {.Name = "Iterators - Primes example", _ .Lambda = New Action(Sub() IteratorAdvancedExample())}
Running the application will product results similar to those seen in Figure 5.14.