C#’s IEnumerable and IEnumerator interfaces are used by collections like Arrays and Lists to standardize methods of looping over them and performing actions, like filtering and selecting with LINQ statements. We’ll discuss how they work, and how to use them.
What Is an Enumerable?
The term “Enumerable” defines an object that is meant to be iterated over, passing over each element once in order. In C#, an Enumerable is an object like an array, list, or any other sort of collection that implements the IEnumerable interface. Enumerables standardize looping over collections, and enables the use of LINQ query syntax, as well as other useful extension methods like List.Where() or List.Select().
All the IEnumerable interface requires is a method, GetEnumerator(), which returns an IEnumerator. So what do Enumerators do?
Well, IEnumerator requires two methods, MoveNext() and Current(). MoveNext is the method used to step over each item, applying any kind of custom iterator logic in the process, and Current is a method used to get the current item after MoveNext is done. You end up with an interface that defines objects that can be enumerated, and how to handle that enumeration.
For example, every time you write a foreach loop, you’re using enumerators. You actually can’t use foreach on a collection that doesn’t implement IEnumerable. When it gets compiled, a foreach loop like this:
Gets turned into a while loop, that processes until the Enumerable is out of items, calling MoveNext each time and setting the iterator variable to .Current.
Most of the time, you want have to manually implement any of this, since Enumerables like Lists are designed to be used in loops and with LINQ methods. But, if you want to create your own collection class, you could do so by implementing IEnumerable and returning the enumerator of the List you store the data in.
For classes that have fields you’d like to iterate over, this can be an easy way to clean up the code. Instead of having the data array be public and iterate over CustomCollection.dataArray, you can just iterate over CustomCollection itself. Of course, if you want it to behave more like a proper list, you’ll need to implement other interfaces like ICollection as well.
LINQ Queries Use Deferred Execution
The most important thing to note about LINQ, is that queries don’t always run right away. Many methods return Enumerables and use deferred execution. Instead of looping right when the query is made, it actually waits until the Enumerable is used in the loop, and then does the query during MoveNext.
Lets look at an example. This function takes a list of strings, and prints each item that is longer than a certain length. It uses Where() to filter the list, and then foreach to print out each element.
This syntax is a bit confusing, especially if you have a habit of using var (which you should definitely not do here), since it can look like you’re generating a new List with Where() and then iterating over that list. In the case of a foreach loop, an IEnumerable can take the place of a List, but it’s not a List—you can’t append or remove items.
C# is smart though, and instead of iterating over the list twice (which would double execution time for no reason, and allocate useless memory), it just chucks the filtering logic into the foreach loop. This here isn’t exactly what your code gets translated to, but it’s a good idea to conceptualize it like this, with the condition and filtering getting smashed inside the foreach.
Under the hood, it does this by prepending the query to the Enumerator, and returning an Enumerable that will use the logic from Where() when calling Enumerable.MoveNext(). If you’re interested, you can take a look at how System.Linq.Enumerable handles it in System.Core.
This can actually have some negative effects if you’re not careful. What if, between the Where() and the execution, the collection was modified? Doing so could cause queries to appear to be performed out of order.
For example, lets give this same function some input, but rather than printing right away, we’ll append a new word to the list before the foreach. At first glance this can look like it will filter out the single letters and output “Hello World,” but it actually outputs “Hello World Banana,” because the filtering doesn’t get applied until MoveNext is called during the actual iteration of the foreach.
This is particularly sneaky, since we’re not even touching the toPrint variable that gets passed to the foreach loop. However, if you instead append .ToList() to the end of the Where function, that will force C# to perform an iteration and return a new list. Unless that’s specifically what you want though, it’s almost always better to save on execution time and heap allocations by using foreach with an Enumerable.
What Are Coroutines?
Coroutines are special functions that can pause execution and return back to the main thread. They’re commonly used for executing long actions that can take some time to finish, without causing the application to hang while waiting on the routine. For example, in games where the framerate of the application matters a lot, large hitches even on the order of a few milliseconds would hurt the user experience.
Coroutines are linked pretty closely to Enumerators, since they’re really just functions that return an enumerator. This defines an iterator method, and you can use this. You don’t have to do much; just change the return type of the function to IEnumerator, and rather than returning something, you can just yield return, which automatically defines it as an enumerator without explicitly doing so.
Then, to break up execution over different frames, you’ll just need to yield return null whenever you’d like to pause execution and run more of the main thread. Then, to use this Enumerator, you’ll need to call the function and assign it to a variable, which you can call MoveNext() on at regular intervals.
If you’re using Unity, they have a coroutine controller that can handle the processing of coroutines for you, just by starting and stopping them with their methods. However, all it’s really doing is just calling MoveNext() once per frame, checking if it can process more, and handling the return value if it’s not null. You can handle it yourself quite easily:
Of course, you may want extra functionality, like yielding child coroutines which you’ll have to add to a processing stack, and yielding expressions such as WaitForSeconds or waiting for async functions to finish, which you’ll just need to check once per frame (before calling MoveNext) if the expression returns true, and then continue processing. That way, the coroutine will halt completely until it can start running again.