#enumerators

1 messages · Page 1 of 1 (latest)

frosty ember
#

thread time

charred jay
#

assume I know nothing about enumerators

#

i mean I dont

#

yeah

low niche
#

An IEnumerable is something that can basically handle returning a collection of data in steps rather than returning them one by one.

frosty ember
#

An enumerator's job is to give you a series of values.

#

(let me do this pls)

charred jay
#

understandable so far

low niche
#

An enumerator is used by the IEnumerable for this process, and contains a method MoveNext and Current to determine where it is with these steps, and if there are values left

frosty ember
#

i don't want to have two or more people simultaneously trying to explain this

low niche
#

You should understand you probably won't use enumerators directly and instead you should probably learn the concept of an IEnumerable

charred jay
frosty ember
#

In C#, an enumerator is something that implements the IEnumerator interface.

There are only three things in the IEnumerator interface.

  • The MoveNext method.
  • The Reset method.
  • The Current property.

Anything that implements these things is an enumerator.

#

(and Reset isn't even implemented half of the time)

charred jay
#

okay, so are these things you can do with the enumerator

frosty ember
#

MoveNext tells the enumerator that you want to move to the next value.

charred jay
#

*in the enumerator

frosty ember
#

It returns true if it has a new value for you and false if it ran out

#

Current provides the current value.

charred jay
#

alright so far

frosty ember
#
public class OnesForever : IEnumerator
{
    public bool MoveNext()
    {
        return true;
    }

    public void Reset()
    {

    }

    public object Current
    {
        get
        {
            return 1;
        }
    }
}
#

Here is a very simple enumerator.

#

actually I can simplify that a bit further

#

there we go

#

It gives you an infinite number of 1s

#

MoveNext does literally nothing and returns true every time

#

so the enumerator says "I have another value for you!" every time

charred jay
#

i see

frosty ember
#

Reset also does literally nothing

#

and Current always returns 1.

charred jay
#

in the

frosty ember
#

beacuse it's part of the interface

#

the compiler would yell at me

charred jay
#

fair enough

frosty ember
#

the method does exist

#

So that's what an enumerator is

frosty ember
charred jay
#

do we also put that because we dont want any errors?

frosty ember
#

We just happen to do nothing in that method.

charred jay
frosty ember
#

No. MoveNext returns whether or not there is another value.

#

Our method always returns true because we always have another value.

#

(and that value is 1)

#

We could make something slightly more complex...

low niche
#

It should be pointed out that an enumerator mostly exists as a general way to return values in steps, and not collections directly. In the case of Coroutines it invokes the code step by step, for example.
If you look at things such as List, it just implements it with a very basic enumerator because it's a basic collection. Nothing suggests it should be this.

frosty ember
#
public class CountUpForever : IEnumerator
{
    int counter = 0;

    public bool MoveNext()
    {
        counter = counter + 1;
        return true;
    }

    public void Reset()
    {
        counter = 0;
    }

    public object Current
    {
        get
        {
            return counter;
        }
    }
}
#

This enumerators produces 1, then 2, then 3, then ...

#

Maybe we could even implement Reset!

charred jay
#

wait I gotta read this again

charred jay
#

the reason I was asking for enumerator was because I wanted some kind of delay when spawning objects

#

i was doing something where I press D and a new object instantiates

frosty ember
frosty ember
charred jay
#

cuz it does nothing

frosty ember
#

so MoveNext needs to actually do something

#

this is not the same logic as OnesForever.

#

these are two different classes that do two different things.

charred jay
#

okay so you used current to repeat the value

#

and movenext to keep moving on

frosty ember
#

the Current property gives you the current value

#

the MoveNext method moves to the next value

#

that's it

#

that's all an enumerator does, ever

charred jay
#

alright

frosty ember
#
IEnumerator CountUpForeverMethod() {
  int counter = 0;

  while (true) {
    counter += 1;
    yield return counter;
  }
}
charred jay
#

yeah

#

ive not seen "while" though

frosty ember
#

when the compiler sees a method that returns IEnumerator and that uses yield, it transforms the method into a class.

frosty ember
frosty ember
#

(and that's how I think about it!)

#
var enumerator = CountUpForeverMethod();

enumerator.MoveNext();
Debug.Log(enumerator.Current); // logs 1
enumerator.MoveNext();
Debug.Log(enumerator.Current); // logs 2
enumerator.MoveNext();
Debug.Log(enumerator.Current); // logs 3
charred jay
#

what does the while mean

frosty ember
#

while is one of C#'s iteration statements, used to repeat something as long as a condition is true

frosty ember
#

while (true) { ... } will execute its body forever

#

So it runs these two lines over and over:

#
counter += 1;
yield return counter;
#

increment counter

#

yield counter

charred jay
#

so it says it adds 1

#

and then yield return means that it returns that counter

#

and then the while makes it repeat

frosty ember
#

right

frosty ember
charred jay
frosty ember
#

the enumerator is done producing its next value

frosty ember
#

if you ask it for its Current value, you'll get whatever you yield return'd

charred jay
#

i see

frosty ember
charred jay
#

ahhh

#

so it moves to the next value and then puts it in the log

#

3 times

frosty ember
#

right

charred jay
#

and then ends

frosty ember
#

then it just..stops calling MoveNext, so the enumerator will do nothing else

#

so, this brings us to unity coroutines

charred jay
#

var enumerator = CountUpForeverMethod();

#

i saw you put this above

#

why dont we just put

#

IEnumerator something()
{
enumerator.MoveNext();
Debug.Log(enumerator.Current); // logs 1
enumerator.MoveNext();
Debug.Log(enumerator.Current); // logs 2
enumerator.MoveNext();
Debug.Log(enumerator.Current); // logs 3
}

frosty ember
#

where did that come from?

charred jay
#

var enumerator = CountUpForeverMethod();
so you just defined it here?

frosty ember
#

all of this code would be inside of a method

#
public void DoStuff() {
  var enumerator = CountUpForeverMethod();
  ...
}
charred jay
#

yeah

frosty ember
#

You could certainly define another method that, itself, uses an enumerator

#
IEnumerator AnotherOne {
  var enumerator = CountUpForeverMethod();
  enumerator.MoveNext();
  yield return enumerator.Current;
}
#

this would yield return 1, then stop producing values

charred jay
#

im understanding so far

frosty ember
#

Unity puts your enumerator into a list.

#

Each frame (by default), it calls MoveNext on every enumerator in the list

#

and throws out anything that returns false

#

It then looks at the Current property of each enumerator.

#

If the enumerator produced a special value (like a WaitForSeconds), Unity changes how often it calls MoveNext.

#

That's it!

charred jay
#

i gotta read that again real quick

craggy quiver
#

And if the enumerator produces anything else that it doesn't recognize, it will wait for one frame. This includes null, which most people use to wait a frame, but you can use anything that isn't a recognized yield instruction.

frosty ember
#

so you can yield return "OH MY GOD DON'T UPDATE ME NEXT FRAME!!!" and it will do the default thing (call MoveNext next frame)

charred jay
frosty ember
#

IEnumerator is allowed to produce literally any value

frosty ember
#

Unity looks at the value you produced. If it's one of the classes listed on this page, then the coroutine gets handled differently.

craggy quiver
frosty ember
#
yield return null; // nothing
yield return 3; // nothing
yield return new WaitForSeconds(1f); // waits for 1 second
yield return new GameObject(); // nothing
charred jay
frosty ember
#

yield return demands a value

#

you can't just write yield return;

#

yield return null; makes it obvious that you are not asking for Unity to do anything special to your coroutine.

frosty ember
#

it produces a series of values

#

it would be weird if it could just..not do that?

#

not even giving you a null: literally not giving you a sequence of things at all

frosty ember
#

Unity takes advantage of how you can write a method that "starts" and "stops"

#

It does use the values produced by your enumerator, but only if they're one of those special classes.

charred jay
craggy quiver
charred jay
#

it waits for one frame? i thought it returns a value of 1

frosty ember
#

in the context of a unity coroutine

charred jay
#

alright

frosty ember
#

by itself, yes, yield return 1; just means that your enumerator's current value becomes 1

charred jay
#

but in a coroutine

#

it waits

#

alright

frosty ember
#

Unity then looks at your current value and says "it's not one of these classes I'm looking for!"

#

so it will call MoveNext on your enumerator at the normal time (one frame later)

frosty ember
#

rather than picking random numbers, or strings, or something

charred jay
frosty ember
#

No.

#

3 is not one of the classes Unity is looking for.

#

It's looking for things like:

  • WaitForSeconds
  • WaitForEndOfFrame
#

3 is not one of these classes. It's an int.

#

yield return 12345; will have the same effect on the coroutine as yield return null;

#

The first one will make your enumerator's Current value become 12345 and the second will make it become null

charred jay
#

OHJ

#

OKAY

frosty ember
#

Unity does not care about that

charred jay
#

that just made sense

craggy quiver
#

This is where the weirdness comes in. If Unity wanted to, they could have implemented it so that you could just return integers or floats and that would be the same as WaitForSeconds, but they didn't. It's arbitrary, whatever some Unity developer decided should be recognized.

frosty ember
#

probably because it'd get ambiguous real fast

charred jay
#

so If I had to wait 1 second

frosty ember
#

yield return 1; i want to wait for one second, not just one frame!

charred jay
#

no wait

charred jay
#

if I wanted it to instantiate and then wait one second

#

would I do

#

Instantiate(gameObject)
yield waitforseconds(1);

frosty ember
#

yield new WaitForSeconds(1f);, but yes

charred jay
#

wow

#

i got confused because

#

I wanted to wait 1 second before another instantiate

#

so it doesnt constantly do it

#

and my friend wrote this

wraith idol
#

have you walked him through everything, fen?

charred jay
#

IEnumerator SpawnCoroutine()
{
while(isSpawning)
{
Instantiate(gameObject)
yield new waitforseconds(0.1f);
isSpawning=false

}
}

#

he wrote that

frosty ember
#

thus exiting the loop

frosty ember
charred jay
#

alright

#

well THANK YOU GUYS

#

I HOPE YOU HAVE A GOOD LIFE

#

A GREAT ONE

#

I will depart now with my new knowledge

wraith idol
#

IEnumerators let you do something like this:
IEnumerator<int> EvenEntries(List<int> myList) {
for (int i = 0; i < myList.Count; i += 2)
yield return myList[i];
}

charred jay
#

i actually have to go for now so ill read this later

wraith idol
#

this is an IEnumerator that outputs all the entries at the even numbered positions of the list

charred jay
#

thanks so much

#

though

frosty ember
#

oh yeah, and one more thing

#

Fused mentioned IEnumerable earlier

#

IEnumerable means "I can give you an enumerator"

#

that's pretty much it

#

a list is enumerable: you can ask it for an enumerator

#

then the enumerator gives you the list's values

wraith idol
#

i use IEnumerables more then IEnumerators

frosty ember
#

yeah, since that lets you use the class with foreach

wraith idol
#

they are very similar, but Ienumerables are nicer to work with because foreach

frosty ember
#

well...

#

to be pedantic, the compiler literally just looks for a GetEnumerator method

#

it doesn't need the IEnumerable interface

wraith idol
#

the syntax is nicer

frosty ember
#

but implementing that interface makes sure that you have the right method

#

and it lets you manually ask for an enumerator

craggy quiver
#

This thread has turned into an IEnumerator Appreciation meeting and I'm not complaining.

frosty ember
#

I have bootleg coroutines in one part of my project

#

a list of IEnumerator

#

coroutines.RemoveAll(x => !x.MoveNext());

#

(I needed something like a coroutine that runs at a very specific point, after my IK systems reported they were all done solving)

craggy quiver
#

Did you know you can implement an enumerator with ref returns? You can make a custom Array enumerator that returns by ref, so you can modify the value in a foreach!

foreach (ref int num in intArray.AsRefEnumerable())
{
    num += 10;
}
frosty ember
#

explode

craggy quiver
#

A custom struct enumerator is my preferred way to expose collections in an API in an optimized way and without needing to expose the collection itself. IReadOnly collections are cool but you can't use them in foreach without allocating for the enumerator.

charred jay
#

ienumerable is for another day perhaps

craggy quiver
#

We've gone way beyond what is needed to use coroutines effectively anyway. Most developers who use coroutines often do not have this deep an understanding of how enumerators work. And they don't need to, you can totally get by with just learning the syntax.

#

But more knowledge doesn't hurt.

charred jay
charred jay
#

alright

#

well thanks for this masterclass