Iterators

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.

The Core Concept

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.

A Basic Iterator Example

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.

Figure 5.12 Basic Iterator example

5.12

An Advanced Iterator Example

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.

Figure 5.13 Oceans example

5.13

Using Iterators

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.

Figure 5.14 Primes example

5.14
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset