Author avatar

Dániel Szabó

forEach and Enumerables in C#

Dániel Szabó

  • Jan 9, 2020
  • 10 Min read
  • 2,053 Views
  • Jan 9, 2020
  • 10 Min read
  • 2,053 Views
Languages Frameworks and Tools
C#

Introduction

This guide revolves around two very old programming concepts that are implemented in most languages nowadays. One of the concepts is generators and the other is iterators. In this guide, we will inspect how these ideas were incorporated into C# and check out how we can utilize them for our own advantage, considering all the pros and cons.

Generators

A generator is usually a function, or a special routine implemented in the language which controls the behavior of a loop. Generators bear an eerie resemblance to functions that return an array as they have parameters and can be called to generate a sequence of items.

Let's say we have a function that returns a list of integers, about one million at a time. Without generators, the list would need to be built up in memory, allocate place for all the values, and then pass those values wherever they need to go. If you have the resources, that's fine; however, people always strive to figure out something for these kinds of problems, and thus generators were born. A generator will yield one item at a time, allowing the caller to get started processing the first values immediately. This requires less memory and allows more expressive control flows in your applications. This feature has been available since version 2.0 of the language.

Iterators

The concept of an iterator dates back as far as 1974 to the CLU programming language. By design, an iterator should allow the programmer to traverse specific containers. Iterators are most commonly used for lists. They are implemented in C# as interfaces and tightly coupled to the container itself in implementation. An iterator allows access to the data held in the container.

upport for iterators came to the language with version 2.0. In C#, iterators are called enumerators, and they come from the IEnumerator interface. Basically, this interface provides a MoveNext() method, which goes for the next element and checks if the end of container or collection is reached. It also has a Current property that allows access to the value of the element currently pointed at. There is also a Reset() method that allows you to move the enumerator back to its initial position. Before the first element can be retrieved, a MoveNext() call is needed as the enumerator points before the first element. You can obtain an enumerator if the GetEnumerator() method of an object or collection implementing the IEnumerable interface is called.

Note: Most of the container classes implement the interface; however, the foreach loop cannot operate on these.

Implementation

First let me show you a demonstration code. The list class is coming from the System.Collections.Generic namespace.

We create a list.

1
2
3
4
5
6
7
8
9
10
List<string> toBeIterated = new List<string>();
toBeIterated.Add("Programming");
toBeIterated.Add("is");
toBeIterated.Add("really");
toBeIterated.Add("fun");
toBeIterated.Add("if");
toBeIterated.Add("you");
toBeIterated.Add("do");
toBeIterated.Add("it");
toBeIterated.Add("right");
csharp

We can convert this list to a generator very simply.

1
IEnumerable<string> iEnumerableOftoBeIterated = (IEnumerable<string>)toBeIterated;
csharp

Using a foreach loop will allow us to traverse the list.

1
2
3
4
foreach(string element in iEnumerableOftoBeIterated)
{  
   Console.WriteLine(element);  
}  
csharp

Executing the code will give us the following result.

1
2
3
4
5
6
7
8
9
Programming
is
really
fun
if
you
do
it
right
bash

This was the generator approach. Now let's see how we can use the iterator approach.

Let's convert our list.

1
IEnumerator<string> iEnumeratorOftoBeIterated = toBeIterated.GetEnumerator();
csharp

As mentioned above, GetEnumerator() will work because the List class from the Generic namespace implements the IEnumerator interface. However, the foreach cannot be used. We need a while loop for this.

1
2
3
4
while (iEnumeratorOftoBeIterated.MoveNext())
{
	Console.WriteLine($"The current value is: {iEnumeratorOftoBeIterated.Current}");
}
csharp

Executing the above code will give us the following.

1
2
3
4
5
6
7
8
9
The current value is: Programming
The current value is: is
The current value is: really
The current value is: fun
The current value is: if
The current value is: you
The current value is: do
The current value is: it
The current value is: right
bash

Note that there is no break statement, yet it does not go into infinite mode.

Note: Both of these approaches have the same goal: to allow the programmer to traverse a container, in our case a list. However, when we use the IEnumerator approach, it only works if the MoveNext() function is called. The main difference between IEnumerable and IEnumerator is that the latter retains the cursor's current state.

A word of advice: If you would like tho sequentially go through a collection, you should use the IEnumerable interface. If you would like to retain the cursor position and pass it between functions, you should use IENumerator.

Let's look at a practical example. We would like to create a small app that can process numbers differently depending on whether they are odd or even. IEnumerator provides us an elegant solution that involves two static methods.

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
using System;
using System.Threading;
using System.Collections.Generic;

namespace generatorsNIterators
{   
    class Program
    {
        static void EvenProcessor(IEnumerator<int> i)
        {              

            if (i.Current == 0)
                {
                    Console.WriteLine("The list was processed, exiting!");
                    Thread.Sleep(10);
                    i.Dispose();
            }
            else if (i.Current % 2 != 0)
                {
                    Console.WriteLine("The number is ODD, calling processor.");
                    OddProcessor(i);
                }
            else if(i.Current % 2 == 0)
            {
                Console.WriteLine($"Processing even: {i.Current}");
                Console.WriteLine("Even number processed!");
                i.MoveNext();
                EvenProcessor(i);
            }           
            else
                { i.Dispose(); }
        }
        static void OddProcessor(IEnumerator<int> i)
        {
            if (i.Current == 0)
                {
                    Console.WriteLine("The list was processed, exiting!");
                    Thread.Sleep(5);
                    i.Dispose();
                }
            else if (i.Current % 2 == 0)
                {
                    Console.WriteLine("The number is EVEN, calling processor.");
                    EvenProcessor(i);
                
            }
            else if(i.Current % 2 != 0)
                {
                    Console.WriteLine($"Processing odd: {i.Current}");
                    Console.WriteLine("Odd number processed!");
                    i.MoveNext();
                    OddProcessor(i);
                }
            else
                { i.Dispose(); }
        }
        static void Main(string[] args)
        {
            List<int> myList = new List<int>(10);
            for (int i = 1; i <= 20; i++) { myList.Add(i); }
            IEnumerator<int> myListEnum = myList.GetEnumerator();
            myListEnum.MoveNext();
            OddProcessor(myListEnum);
            Console.ReadKey();
            }
    }
}
csharp

When we execute this code, the following is on our console.

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
Processing odd: 1
Odd number processed!
The number is EVEN, calling processor.
Processing even: 2
Even number processed!
The number is ODD, calling processor.
Processing odd: 3
Odd number processed!
The number is EVEN, calling processor.
Processing even: 4
Even number processed!
The number is ODD, calling processor.
Processing odd: 5
Odd number processed!
The number is EVEN, calling processor.
Processing even: 6
Even number processed!
The number is ODD, calling processor.
Processing odd: 7
Odd number processed!
The number is EVEN, calling processor.
Processing even: 8
Even number processed!
The number is ODD, calling processor.
Processing odd: 9
Odd number processed!
The number is EVEN, calling processor.
Processing even: 10
Even number processed!
The list was processed, exiting!
bash

What happens here? We have a list with numbers from 1 to 20, and this list is converted to become an iterator with the following statement.

1
IEnumerator<int> myListEnum = myList.GetEnumerator();
csharp

This is needed because we have two static methods, EvenProcessor and OddProcessor. Both take an IEnumerator argument, which is the Current position of our cursor. This solution does not use any loops. It works simply through the magic of iterators. Our list is only 10 items long, and if the number is even the appropriate function will process it. The same is the case with the odd numbers. The key here is the i.Current and the fact that the position of the cursor is kept.

Conclusion

All in all, generators and iterators are, in my opinion, very useful. They allow you to create clean control flow in your applications and yet keep the code clean. In this guide, we defined generators and iterators, exploring where they come from and why. Then we built up our knowledge, ending with a complete demonstration. The coolest part is that we were able to process a list without for, while, or do while loops involved. I hope this has been informative and you found what you were looking for. If you liked this guide, give it a thumbs up and stay tuned for more, or check out my other guides.

23