C# IEnumerable and IEnumerator

C# Code Snippets IEnumerable IEnumerator and Yield
Share:

About

In this code snippet, we will take a look at the IEnumerator and IEnumerable in C#.

Making an object enumerable will allow us to iterate over it with something like the foreach loop. IEnumerable is an interface that specifies what has to be implemented to make an object iterable(an object that can be iterated over). For an object to be iterable it has to have a GetEnumerator() method that returns an object of type IEnumerator.

You could define your own enumerator(for more information see Microsofts documentation) by making a class that implements the IEnumerator interface. However, you should use yield return instead as it greatly simplifies the process. We’ll look at the yield keyword more closely in the second part of this post.

The great thing about using this iterator design pattern is that you can do deferred loading/execution. Let’s say we want to generate a 1000000 random numbers and do something with them. Normally you would generate all of them, save them in memory, and then proceed to use them. This uses up a lot of memory and it means that all the numbers have to be generated before you can start processing them. 

Using the iterator pattern allows us to instead generate a number, process it as part of the iteration step and move on to the next number in the next iteration. This way only one number at a time is held in memory and you can immediately do something with the processed data like sending it over the network or writing it to a database or file.

The same idea applies if you are making large queries to a database or reading/writing any other I/O like files, etc. LINQ is a great example of where this concept/design pattern is also used.

Let’s have a look at the code below to see how to make an object enumerable.

Once you implement the interface you can just press ctrl + .  select “implement interface” and IntelliSense will automatically make the required GetEnumerator() method.

Code:

using System;
using System.Collections;
using System.Collections.Generic;

namespace IEnumerableExample
{
    class Program
    {
        static void Main(string[] args)
        {
            MyClass MC = new MyClass();

            //Fill up the list.
            MC.Mylist = new List<int>() { 10, 5, 15, 20 };

            //We can now use foreach on our object.
            foreach (var item in MC)
                Console.WriteLine(item);


            Console.WriteLine("----");


            MySecondClass MSC = new MySecondClass();

            //Fill up the array.
            MSC.MyArray = new int[5] { 10, 5, 2, 3, 7 };

            //We can now use foreach on our object.
            foreach (var item in MSC)
                Console.WriteLine(item);

            Console.ReadLine();
        }
    }

    //Implement the IEnumerable interface.
    class MyClass : IEnumerable
    {
        public List<int> Mylist { get; set; }

        //Here we define the Enumerator for our class. 
        //The class can now be used with something like foreach as it relies on the GetEnumerator.
        public IEnumerator GetEnumerator()
        {
            //When foreach gets used on an instance of this class we want to iterate through the MyList List<>.
            //Lists have their GetEnumerator already set. So we will just get GetEnumerator of our list and return that.
            return Mylist.GetEnumerator();
        }
    }

    //Implement the IEnumerable interface and our own IEnumerator with the help of the yield keyword.
    class MySecondClass : IEnumerable
    {
        public int[] MyArray { get; set; }

        //Here we've implemented the enumerator so our class is enumerable and we can use foreach with it.
        //We could just call GetEnumerator() on the array to get its enumerator(as arrays implement IEnumerable already) like we did for the list in the example above.
        //But we'll write our own enumerator with the help of the yield keyword for demonstration purposes.
        public IEnumerator GetEnumerator()
        {
            //Let's iterate over the array and get its values.
            for (int i = 0; i < MyArray.Length; i++)
            {
                int value = MyArray[i];

                //... we could add some additional logic or calculations here if needed ...

                //Finally we yield return the value.
                yield return value; //See the code in the next section of this post to see how yield works.
            }
        }
    }
}

Resulting output:

Yield Keyword Code:

using System;
using System.Collections.Generic;

namespace yieldKeyword
{
    class Program
    {
        static void Main(string[] args)
        {
            foreach (var number in yieldDemo())
                Console.WriteLine(number);


            Console.WriteLine("--------");


            var rng = new RandomNumberGenerator();

            //Iterate over IEnumerable type.
            foreach (var number in rng.GenerateRandomNumbers(0, 10, 5))
                Console.WriteLine(number);


            Console.WriteLine("--------");


            //Keep iterating over the IEnumerable type until it manages to generate the number 15.
            foreach (var number in rng.GenerateRandomNumbersUntil(0, 10, 7))
                Console.WriteLine(number);


            Console.ReadLine();
        }

        public static IEnumerable<int> yieldDemo()
        {
            yield return 1;
            yield return 3;
            yield return 3;
            yield return 7;
        }

        class RandomNumberGenerator
        {
            public RandomNumberGenerator()
            {
                random = new Random();
            }


            private readonly Random random;


            public IEnumerable<int> GenerateRandomNumbers(int min, int max, int number)
            {
                for (int i = 1; i <= number; i++)
                {
                    //Generate a random number between the min and max value.
                    int randomNumber = random.Next(min, max);

                    //return each generated number one at a time using yield.
                    yield return randomNumber;
                }
            }

            public IEnumerable<int> GenerateRandomNumbersUntil(int min, int max, int match)
            {
                while (true)
                {
                    //Generate a random number between the min and max value.
                    int randomNumber = random.Next(min, max);

                    //Numbers will keep getting generated and returned until 
                    if (randomNumber == match)
                        yield break; //Stop the iteration when we get to the desired number.
                    else
                        yield return randomNumber; //return each generated number one at a time using yield.
                }
            }
        }
    }
}
The yield keyword is just “syntactic sugar” for a state machine. The working principle is similar to the way that async/await works. If we look at the lowered code of the method yieldDemo() from the code below using SharpLab we can see what’s actually going on in the background.
You can see that a state machine and an iterator(similar to the implementation described here) are automatically created when the code gets lowered by the compiler.
I think this video where I use the debugger to step through the code line by line better illustrates how yield works.

Resulting output:

Share:

Leave a Reply

Your email address will not be published. Required fields are marked *

The following GDPR rules must be read and accepted:
This form collects your name, email and content so that we can keep track of the comments placed on the website. For more info check our privacy policy where you will get more info on where, how and why we store your data.

Advertisment ad adsense adlogger