All C# developers faced these interfaces at some point, but how to use them? And better than that, how to use them properly? — Because I’ve seen a lot of mistakes with these guys.
The main point of writing this article is the fact that I lost the count about how many times I’ve seen mistakes and misunderstoods related to C# collections, so I hope to clarify some points here.
Before we start, let’s go a little back and see the cover image at the beginning of the text. It’s a visualization about how you may thinking about IEnumerables
.
It works like a Turing Machine tape with a cursor that start pointing to null
. This cursor move only straight forward (to right in the image), without index access and besides that, only the necessary information to iterate the collection is provided by an IEnumerable
.
Having said that, we can start to code. Let’s check the IEnumerable
implementation (with and without generics):
And that’s all. There’s no one more line code at IEnumerable
implementation.
What does this suggest?
IEnumerable
actually is a IEnumerator
factory. Now we need to check IEnumerator
out. It’s a little bit more complex, but we can handle it.
There’s two different interfaces, with and without using generics, just like IEnumerable
.
The IEnumerator
is the one who provides the information about how to iterate a collection (and IEnumerable
that is the famous one, how unfair is that?).
In fact, when we use the foreach
loop, C# internally calls the GetEnumerator
of the collection we’ll iterate and at each step it calls the MoveNext
method to move the cursor forward.
Let’s code a super simple code to iterate an IEnumerable of integers using a foreach loop:
It’s pretty simple, isn’t?
As I said before, this code is a simple syntax sugar to another code a little bit more complex, but still a simple code snippet:
This code is a lot more uglier but it does the exactly same thing. The first step got the enumerator using GetEnumerator
method, after that, we can use the MoveNext
method to iterate each of IEnumerable
elements.
It’s interesting to notice that the MoveNext
method is called even before we got the first element. It occurs because the enumerator cursor start pointing to one position before the first element (as shown in the tape image).
The MoveNext
method have a design that I particularly don’t like. It does two different things.
- One of these things is to move the cursor to the next position, which is the main purpose;
- The secondary thing is what this method returns. It returns
true
if the cursor is pointing to a valid position after it was moved or it will returnfalse
if isn’t.
My problem with the design of this method is the fact that the main operation, move the cursor to the next element, is caused only by a side effect, but this is a topic for another article, let’s move on.
After call the MoveNext
method the first time, we’ll move the cursor to the very first position of the IEnumerable
, like the image below:
And the property Current
just returns the value at the position that the cursor points to.
Having said that, how about to create a brand new collection containing all integer positive numbers that exists?
Yeah, you read it correctly, a collection with all numbers, which means, a infinity collection.
Here’s a thing before we start to code, the whole point of this implementation is see how
IEnumerator
andIEnumerable
works under the hood with aforeach
loop, so, don’t take it as the best implementation possible for a new collection, it’s just an experiment, right?
Let’s start with an IEnumerator
, so, just create a new class that implements IEnumerator<int>
, It will looks like the code below:
So, now we need to create our own stuff now. In order to create an infinite collection, we’ll use the cursor itself as a value.
Let’s create a new field called _current
which starts at -1
and will be incremented as the MoveNext
method is called (usually the value is stored at the IEnumerable
, but this isn’t a regular example, remember?)
Ok, but let’s understand why _current
starts with -1
instead of zero. The answer is pretty simple, it’s because the MoveNext
method is called before the iteration.
Now, let’s go to MoveNext
method, you probably already know what to do here. We need to increase the _current
value and always return true, after all, it’s an infinite collection, so always is possible to move to the next one.
We don’t need to worry about the Dispose
method now, so let’s implement the Reset
method by reseting the _current
to its initial value.
Now, let’s implement our collection itself. It’ll be easy, trust me. All we need is implement the IEnumerable<int>
interface and return an instance of our IEnumerator
.
Maybe for some reason, you may need return new instances any time you call GetEnumerator
, but for our example, only one instance is enough.
Now we can use our own class inside of a foreach
loop:
And we can get the next, the next, and the next and keep doing that forever.
How cool is that?
So, everything was implemented correctly, right? — Not exactly.
Let’s force an interruption by using break
:
BANG! — An exception was thrown.
Why this exception was thrown? — Simple, the Dispose
method wasn’t implemented yet.
Right, but when it’s called?
Just after the loop! In order to remember, let’s reimplement it without foreach
sugar syntax, in other words, by using while
loop:
Now the things are more clear, aren’t they?
When we iterate across an IEnumerable
we use the using
keyword, and as you probably already know, the Dispose
method is called at the end of the using scope.
The reason why the Dispose
method is called is a lot more clear with this syntax, even though it looks worse. We can fix it just by calling Reset
method at Dispose
.
Warning
This isn’t the best way to implement the
Dispose
method whatsoever, this is just an experiment, ok?
Now, you have your own infinite collection, it’s time to snapping your fingers (Like Thanos did)!
See you in the next post!
Top comments (0)