IEnumerable like a class

Hi,

I’ve just finished the lecture “Extensible Modifier System” in the RPG core combat course.
It’s everything pretty clear, except for the fact that IEnumerable is treated exactly like a class.

I understand that in the case of this lecture it’s preferable to use IEnumerable since we can use a foreach loop and it doesn’t bring the overhead of a list, that’s ok.

But IEnumerable is an interface (which just requires the class that agrees to its contract to have a GetEnumerator() method), and returning a IEnumerable we are basically assuming that float implements that interface, am I right?

But float is a struct and it doesn’t implement any GetEnumerator() method.

We are using IEnumerable exactly like a List<> class but a List is actually a class, while IEnumerator is an interface. I’m pretty confused on this point!

Just to be clearer, in the other cases where, in the course, we were using an interface, we already knew that some classes were implementing that interface and we knew what to expect when calling the interface methods.

We can use IEnumerable like that because the classes implement the interface. Basically, we can use anything that the class derives from, interfaces included

For example, when we have an Animal class, and from it we have derived a Cat and a Dog. We can have a function that accepts Cat and we will be unable to pass Dog into that function. Or we can have a function that accepts Animal and we can pass both Cat and Dog into that function. It is the same with interfaces. List and Array both implement IEnumerable and therefore we can pass a List or an Array and not be restricted.

I’m not sure which part you are referring to, here, but we never assume float implements IEnumerable because it doesn’t and the code won’t compile.

We still know what to expect. Since we are asking for IEnumerable, we know that anything that’s in IEnumerable will be available. That’s the whole point of interfaces

Let’s take a moment to get into the weeds of what an IEnumerable actually does: This is the actual code for the IEnumerable interface:

public interface IEnumerable<out T> : IEnumerable
{
  IEnumerator<T> GetEnumerator();
}

So an IEnumerable<T> must declare a method

public IEnumerator<T> GetEnumerator();

Of course, that leads us to the question… what is an IEnumerator?

public interface IEnumerator<out T> : IEnumerator, IDisposable
{
  T Current { get; }
}

So an IEnumerator<T> must have the property

public T Current;

It also must implement the basic IEnumerator interface:

public interface IEnumerator
{
  object Current { get; }

  bool MoveNext();

  void Reset();
}

Lots of interface methods going on here, but fortunately, we don’t have to deal with any of them. I just want to point out two specific items…

T Current {get;}

and

bool MoveNext();

These are the workhorse methods of all IEnumerables, even though we don’t use them directly (well… we CAN use them directly, actually, but it’s much easier to let foreach handle it).

Imagine you have a deck of Cards, all of which are shuffled. We’re going to deal them out one at a time. You take the first card off of the deck and deal it to the first player. This card is the Card Current. You’ve dealt the card, so you want to Move on to the Next card… (MoveNext();)

public void DealCards(IEnumerable<Card> cards)
{
    Card card = cards.Current;
    DealCardToNextPlayer(card); //The code for this is not important to the story
    cards.MoveNext();
}

Now we’re playing a game of Go Fish, so we need all of the cards to be distributed to the players… This means we need to go through the entire collection of cards… this means that each time we MoveNext(), we need to see if there are any more cards to distribute. The MoveNext() method, in addition to moving on to the next card, returns a bool… true if we were able to get another card, and false if we’re out of cards. We can use this to continue to deal all of the cards out onto the table.

public void DealCards(IEnumerable<Card> cards)
{
    Card card = cards.Current;
    DealCardToNextPlayer(card); //The code for this is not important to the story
    while(cards.MoveNext())
    {
         Card card = cards.Current;
         DealCardToNextPlayer(card);
    }
}

Of course, we normally wouldn’t access an IEnumerable this way. It’s much easier to use the foreach method to access all of the members:

public void DealCards(IEnumerable<Card> cards)
{
    foreach(Card card in cards)
    {
         DealCardToNextPlayer(card);
    }
}

This works because under the hood, foreach uses the IEnumerator interface to cycle through the IEnumerable in exactly the same way as my original example.

The beauty of the IEnumerable interface is that we don’t really care what the source of the Cards (or whatever item you’re using) is. It could be an array… it could be all of the values in a Dictionary, it could be a List, or it could be a database Query result (usually a Linq expression). A great example of how it’s used in this course is in getting stat modifiers… the stat modifiers could come from any IModifierProvider source. We don’t care how the items got into the IEnumerable, we just get to iterate through them just like if they were already an unindexed list.

The downside to an IEnumerable is that you cannot use the IEnumerable interface to insert records, or even delete records, or to refer to them by their index within the collection. You have to iterate through the collection one at a time in order, full stop. (That being said, there are actually Linq extensions that can do amazing things with IEnumerables, and even convert them to lists).

This is why if you are doing a foreach loop on a List, you cannot add or delete elements in that list. You can’t even change the values in the list, unless you are changing a variable or property on a referenced object in the list. This is because foreach is not working on the List as a List, but because List actually implements IEnumerable. The List’s implementation of the IEnumerable interface simply returns each record one by one.

No, we are assuming that the items returned by the IEnumerable<float> via the Current and MoveNext() methods will be floats. An IEnumerable<int> assumes that the results will be ints, etc.

It’s actually the other way around in this case… when we use foreach loops, we use Lists as if they were IEnumerables

When accessing any class or collection by it’s interface, you know exactly what is in the interface, nothing more, nothing less. If the interface has one method in it, that is all you will know about the class that is implementing the interface. You will, however, know that that method or property will be in the class, callable from the reference…

1 Like

Ok, I think I understand the subject a bit better than before for sure!
Thanks for the kind and clear answers!

Best,

Jader

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.

Privacy & Terms