Iterators

Added in .NET 2.0
An iterator is a method, operator or get accessor that performs a custom iteration over a collection class or array.
Using an iterator block is a way of creating an enumerator for a collection type


In C# you usually use foreach to iterate through a collection
This collection must implement the IEnumerable interface
This interface includes the GetEnumerator method which is often very tedious to implement
A new construct called an iterator block has been added to simplify this.


Iterator Pattern

The iterator pattern is encapsulated in the IEnumerable and IEnumerator interfaces, and their generic counterparts.
Any object that inherits the IEnumerable interface can be iterated through.
In this particular case we have a collection object MyCollection that implements IEnumerable.
You cannot iterate through the IEnumerable object directly.
If you had two loops iterating the same collection at the same time they would interfere with each other.
For this reason you need to use an IEnumerator to keep track of where you are in the loop.

System.Collections.IEnumerator<string> iterator = MyCollection.GetEnumerator(); 
while (iterator.MoveNext())
{
    string data = iterator.Current;
}

This code does not call the Dispose method on the iterator, but instead uses a try/finally construct to make sure that the iterator is disposed of when we have finished.



Iterator Blocks

Introduced in C# 2.0
Using an iterator block is a simpler way of creating an enumerator for a collection type
The introduction of the yield keyword defines this block of code as a yield block.
When GetEnumerator is called, the code in the method that contains the yield statement is not actually executed at that point in time.
Instead the compiler generates an enumerator class and that class contains the yield block code
Yield statements allow you to write iterators in a single method with the compiler doing some of the work for you.


class MyClass 
{
    static void Main()
    {
        foreach (string x in ReturnEnumerable())
        {
            System.Console.WriteLine(x);
        }
    }

    static System.Collections.Generic.IEnumerable<string> ReturnEnumerable()
    {
        yield return "start";
        
        for (int i=0; i < 3; i++)
        {
            yield return i.ToString();
        }
        yield return "end";
    }
}

Main executes ReturnEnumerable() creates a new instance of the extra type generated by the compiler and calls MoveNext.
The iterator executes code until it reaches a yield statement.
The iterator remembers that the current item should be "start" and returns true to indicate that there is data available.
Main uses the Current property to retrieve the data, then prints it out.
Main calls MoveNext() again
The iterator continues execution from the point it had previously reached - in other words, it goes to the line after the first yield return. As before, it executes code (initialising the i variable) until it reaches the next yield statement.
The pattern repeats, until there's a call to MoveNext() which reaches the end of the method - at which point the call returns false to indicate that there's no more data available.


There are three very important (related) points here:
First, that none of our original source code is executed until the first call to MoveNext().
Second, that subsequent calls to MoveNext() effectively jump back into the source code at the point they left off before. Yielding a value is sort of like "pausing" the method.
Third, the compiler makes sure that the state of the method (in terms of local variables) is preserved - even though the i variable is local inside the iterator block, its value is kept inside the iterator so that next time MoveNext() is called, it can still be used.


You can use yield break statement to end the iteration.


LINQ to Objects




© 2024 Better Solutions Limited. All Rights Reserved. © 2024 Better Solutions Limited TopPrevNext