A few posts back, we talked about Arrays and Collections, and how easy they were to deal with.

C# in Simple Terms - Arrays and Collections
Let’s learn about arrays, lists, ranges, indices, List<T>, Dictionary<TKey, TValue>, Stack<T>, and more!

In this post, we'll talk about a feature of C# that allows us developers to iterate over many different kinds of collections and return elements from them one-by-one. Let's learn about iterators!

A set of long thin candles are held in clasped hands, ordered by color from pink and red to blue and purple.
Pink, pink, red, red, orange, orange, yellow, yellow... Photo by Sharon McCutcheon / Unsplash

The yield Keyword

Iterators in C# take advantage of a special keyword: yield. The yield keyword is used exclusively when iterating over collections and arrays, and allows us to return elements in a collection or array individually.

To demonstrate this, let's consider the Fibonacci sequence, where each number is the sum of the previous two numbers. The sequence begins like this:

0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55...

Let's build a C# method to calculate the Fibonacci sequence to a specified number of places.

public static IEnumerable<int> Fibonacci(int length)
{
    int a = 0, b = 1;
    for (int i = 0; i < length; i++)
    {
        yield return a;
        int c = a + b;
        a = b;
        b = c;
    }
}

We should note two things about this code block. First of all, the return type of IEnumerable<int> states that the return type will be a collection of integers.

We should also note the strange placement of the yield return a; line of code. This line tells the compiler that when an iterator iterates over this collection, the value it will get will be the value of a at this point. We have placed it before the other lines inside the for loop because we want to return the value of a before it is changed.

What's most interesting about this yield return code block is how we can use it in a foreach loop. We can run this method to show the first ten numbers in the Fibonacci sequence like so:

static void Main(string[] args)
{
    // Display the Fibonacci sequence up to the tenth number.
    foreach (int i in Fibonacci(10))
    {
        Console.Write(i + " ");
    }
}

Which results in this output:

0, 1, 1, 2, 3, 5, 8, 13, 21, 34

We can even have multiple yield return statements in a single code block, such as this example:

public static IEnumerable<int> SomeNumbers()
{
    yield return 1;
    yield return 2;
    yield return 7;
    yield return 8;
    yield return 5;
}

The object returned from this method is of type IEnumerable<int>, which means we can treat it as a collection of integers and output each number, or use LINQ to find the average of all the numbers.

var someNumbers = SomeNumbers();

string allNumbers = "";
foreach(int number in someNumbers)
{
    allNumbers += " " + number.ToString();
}

Console.WriteLine(allNumbers.Trim());
Console.WriteLine("Average: " + someNumbers.Average());

In short, the yield keyword allows us to return elements in a collection one-by-one to a calling method, and the IEnumerable<T> interface allows us to specify that a collection of objects can have an iterator.

Now that we know how the yield keyword works, we can learn about iterators themselves.

Iterators

Like the indexers from the previous post, iterators are most useful on collections. Let's create a custom MonthsOfTheYear class which holds a list of the months of the year.

public class MonthsOfTheYear : IEnumerable
{
    private string[] _months = 
    { 
        "January", 
        "February", 
        "March", 
        "April", 
        "May",
        "June", 
        "July",
        "August",
        "September",
        "October",
        "November",
        "December"
    };
}

We can now create an iterator for this class which will yield return each month name in the private _months array.

public class MonthsOfTheYear : IEnumerable
{
    //...Other properties and methods
    public IEnumerator GetEnumerator()
    {
        for (int index = 0; index < _months.Length; index++)
        {
            // Yield each month of the year.
            yield return _months[index];
        }
    }
}

We can then instantiate an object from the MonthsOfTheYear class and iterate over each name:

MonthsOfTheYear months = new MonthsOfTheYear();

foreach(var month in months)
{
    Console.WriteLine(month);
}

In essence, this allows us to treat the instance of MonthsOfTheYear as though it itself is a collection.

Iterators and Generic Collections

We can also create iterators on custom generic collections.

A few posts ago, we discussed generics. In that post, we created a class StackQueue<T> that could both enqueue elements (put them at the "back of the line") and push elements (put them at the "front of the line").

The class we wrote, StackQueue<T>, looked like this:

public class StackQueue<T>
{
    private List<T> elements = new List<T>();

    public void Enqueue(T item)
    {
        Console.WriteLine("Queueing " + item.ToString());
        elements.Insert(elements.Count, item);
    }

    public void Push(T item)
    {
        Console.WriteLine("Pushing " + item.ToString());
        elements.Insert(0, item);
    }

    public T Pop()
    {
        var element = elements[0];
        Console.WriteLine("Popping " + element.ToString());
        elements.RemoveAt(0);
        return element;
    }
}

We will modify this class so that we can implement an iterator.

Modifying StackQueue<T>

The very first thing we need to do is to have our StackQueue<T> class implement the IEnumerable<T> interface.

public class StackQueue<T> : IEnumerable<T>

Now we need a private variable _top that tracks the number of elements in the collection. We will use this in our iterator to know when to stop getting values out of it.

To implement this, we need our Enqueue(), Push(), and Pop() methods to increment or decrement _top. Our modified class now looks like this:

public class StackQueue<T> : IEnumerable<T>
{
    private List<T> elements = new List<T>();
    private int _top = 0; //NEW

    public void Enqueue(T item)
    {
        Console.WriteLine("Queueing " + item.ToString());
        elements.Insert(elements.Count, item);
        _top++; //NEW
    }

    public void Push(T item)
    {
        Console.WriteLine("Pushing " + item.ToString());
        elements.Insert(0, item);
        _top++; //NEW
    }
    
    public T Pop()
    {
        var element = elements[0];
        Console.WriteLine("Popping " + element.ToString());
        elements.RemoveAt(0);
        _top--; //NEW
        return element;
    }
}

Now we can implement our iterators, again using the yield keyword:

public class StackQueue<T> : IEnumerable<T>
{
    //...Other properties and methods
    
    public IEnumerator<T> GetEnumerator()
    {
        for(int index = 0; index < _top; index++)
        {
            yield return elements[index];
        }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

Finally, we can instantiate, populate, and iterate over a StackQueue<T> instance like this:

var myStackQueue = new StackQueue<int>(); //T is now int

myStackQueue.Enqueue(1);
myStackQueue.Push(2);
myStackQueue.Push(3);
myStackQueue.Enqueue(4); 

//At this point, the collection is { 3, 2, 1, 4 }

foreach(var item in myStackQueue)
{
    Console.WriteLine(item);
}

Glossary

  • Iterators - Objects which "iterate" or move through collections, returning individual elements of said collection.

New Keywords

  • yield - Used to identify an iterator. Tells the compiler to return elements in a collection one-by-one to a calling method.

Summary

The yield keyword allows us to return elements in a collection or array one-by-one to a calling method. We use yield to construct iterators, which iterate over collections. Iterators in a class behave much like properties of that class, and enable the class to be used in foreach loops with minimal code. Finally, iterators can also be used in custom generic collection classes like StackQueue<T>, we just need a variable to keep track of the elements in the collection.

Need some help learning about iterators? I'd love to assist! Ask questions in the comments below!

This is last post in the C# in Simple Terms mega series. Thanks for reading! As this post is scheduled to come out on December 21st, the blog will be on hiatus until after the first of the year. We'll see you all in 2021!

Happy Coding!