C #’s IEnumerable and IEnumerator interfaces are used by collections like arrays and lists to standardize the looping methods on them and perform actions, like filtering and selecting with LINQ statements. We will 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, passing over each element once in order. In C #, an Enumerable is an object such as an array, list, or any other type of collection that implements the IEnumerable interface. Enumerables normalize the loop over collections and allow the use of LINQ query syntax, as well as other useful extension methods such as List.Where () or List.Select ().
The IEnumerable interface requires only one 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 iterate through 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 complete. You end up with an interface that defines what objects can be enumerated and how to handle that enumeration.
For example, every time you write a foreach loop, you are using enumerators. You actually can’t use foreach on a collection that doesn’t implement IEnumerable. When it is compiled, a foreach loop like this:
foreach (Int element in list)
// your code
Gets transformed into a while loop, which processes until the Enumerable runs out of items, calling MoveNext each time and setting the iterator variable to .Current.
IEnumerator enumerator = list.GetEnumerator ();
while (list.MoveNext ())
element = (Int) enumerator.
// your code
Most of the time, you want to have to manually implement all of this, because enumerables like lists are designed for use in loops and with LINQ methods. But if you want to create your own collection class, you can do so by implementing IEnumerable and returning the enumerator of the list you’re storing the data in.
For classes with fields that you want to iterate over, this can be an easy way to clean up the code. Instead of the data array being public and iterating over CustomCollection.dataArray, you can just iterate over CustomCollection itself. Of course, if you want it to behave more like an appropriate list, you will also need to implement other interfaces like ICollection.
LINQ queries use lazy execution
The most important thing to note about LINQ is that queries don’t always get executed right away. Many methods return Enumerables and use lazy execution. Instead of looping to the right when the request is made, it actually waits until the enumerator is used in the loop and then performs the request during MoveNext.
Let’s 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, then foreach to print each item.
This syntax is a bit confusing, especially if you’re used to using var (which you definitely shouldn’t be doing here), as it might seem like you’re generating a new list with Where () and then iterating over this list. In the case of a foreach loop, an IEnumerable can replace a list, but it is not a list. You cannot add or remove items.
C # is smart though, and instead of iterating twice over the list (which would double the execution time for no reason and allocate unnecessary memory), it just throws the filter logic into the foreach loop. It’s not exactly what your code is translated to, but it’s a good idea to conceptualize it like this, with the condition and filtering destroyed inside the foreach.
Under the hood, it does this by adding the query to the enumerator and returning an Enumerable which will use the logic of Where () when calling Enumerable.MoveNext (). If you are interested, you can find out how System.Linq.Enumerable manages it in System.Core.
It can actually have negative effects if you’re not careful. What if, between Where () and execution, the collection changes? This could give the impression that the queries are being executed out of order.
For example, let’s give this same function an input, but rather than printing right away, we’ll add a new word to the list before the foreach. At first glance, this might appear to filter out single letters and display “Hello World”, but it actually returns “Hello World Banana” because filtering is not applied until MoveNext is called during the iteration. real foreach.
This is particularly sneaky, because we don’t even touch the toPrint variable which is passed to the foreach loop. However, if you add .ToList () to the end of the Where function instead, it will force C # to iterate and return a new list. Unless this is specifically what you want, it is almost always best to save on execution time and heap allocations by using foreach with an Enumerable.
What are coroutines?
Coroutines are special functions that can interrupt execution and return to the main thread. They are commonly used to perform long actions that can take a long time to complete, without the application crashing while waiting for the routine. For example, in games where application framerate matters a lot, big issues even on the order of a few milliseconds would hurt the user experience.
Coroutines are quite closely related to enumerators, because they are really just functions that return an enumerator. This defines an iteration method, and you can use it. 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 return return, which automatically defines it as an enumerator without doing so explicitly.
Then to split the execution on different images, you just need to return null every time you want to pause execution and run the main thread further. Then, to use this Enumerator, you will need to call the function and assign it to a variable, on which you can call MoveNext () at regular intervals.
If you are 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, it only calls MoveNext () once per frame, check if it can process more, and handle the return value if it’s not zero. You can manage it yourself quite easily:
Of course, you might want additional features, such as producing child coroutines that you will need to add to a processing stack, and producing expressions such as WaitForSeconds or waiting for asynchronous functions to complete, which you will just need to check once per image. (before calling MoveNext) if the expression returns true, then continue processing. This way the coroutine will stop completely until it can start working again.