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.
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. } } } } }