#enumerators
1 messages · Page 1 of 1 (latest)
An IEnumerable is something that can basically handle returning a collection of data in steps rather than returning them one by one.
understandable so far
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
ðŸ˜
i don't want to have two or more people simultaneously trying to explain this
You should understand you probably won't use enumerators directly and instead you should probably learn the concept of an IEnumerable
im with you till here
In C#, an enumerator is something that implements the IEnumerator interface.
There are only three things in the IEnumerator interface.
- The
MoveNextmethod. - The
Resetmethod. - The
Currentproperty.
Anything that implements these things is an enumerator.
(and Reset isn't even implemented half of the time)
okay, so are these things you can do with the enumerator
MoveNext tells the enumerator that you want to move to the next value.
*in the enumerator
It returns true if it has a new value for you and false if it ran out
Current provides the current value.
alright so far
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
i see
fair enough
i should say that it just throws an exception half of the time
the method does exist
So that's what an enumerator is
now you will notice that this does not line up with what you're used to seeing in Unity code
you said even movenext does nothing
do we also put that because we dont want any errors?
Right. The interface demands it.
We just happen to do nothing in that method.
do we always put Return true to reset the enumerator?
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...
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.
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!
wait I gotta read this again
yeah
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
var enumerator = new CountUpForever();
enumerator.MoveNext();
Debug.Log(enumerator.Current); // logs 1
enumerator.MoveNext();
Debug.Log(enumerator.Current); // logs 2
enumerator.MoveNext();
Debug.Log(enumerator.Current); // logs 3
so let me re-write this as something more familiar
I thought we dont change movenext
cuz it does nothing
this enumerator's job is to count up from zero.
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.
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
alright
You are probably used to seeing this:
IEnumerator CountUpForeverMethod() {
int counter = 0;
while (true) {
counter += 1;
yield return counter;
}
}
when the compiler sees a method that returns IEnumerator and that uses yield, it transforms the method into a class.
in fact, the class it produces will look a lot like this!
You can imagine that the method just "pauses" every time it hits a yield statement, then "resumes" when you call MoveNext() on the enumerator.
(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
what does the while mean
while is one of C#'s iteration statements, used to repeat something as long as a condition is true
ohh
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
so it says it adds 1
and then yield return means that it returns that counter
and then the while makes it repeat
right
yield return is like reaching the end of MoveNext() up here
so I can return it to the console if I wanted
the enumerator is done producing its next value
yeah
if you ask it for its Current value, you'll get whatever you yield return'd
i see
well, you could get the value out, then log the value
just like this
right
and then ends
then it just..stops calling MoveNext, so the enumerator will do nothing else
so, this brings us to unity coroutines
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
}
well, what's enumerator?
where did that come from?
var enumerator = CountUpForeverMethod();
so you just defined it here?
all of this code would be inside of a method
public void DoStuff() {
var enumerator = CountUpForeverMethod();
...
}
yeah
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
yeah
im understanding so far
https://docs.unity3d.com/ScriptReference/MonoBehaviour.StartCoroutine.html
StartCoroutine takes an IEnumerator.
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!
i gotta read that again real quick
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.
Yep -- if it doesn't know what it's looking at, you get the default behavior.
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)
this bit is slightly weird to me, I dont get what you mean by special value
notice how the return type of the Current property is object
Unity looks at the value you produced. If it's one of the classes listed on this page, then the coroutine gets handled differently.
It's normal for this bit to be weird. Unity coroutines make use of enumerators in an unconventional way.
i see
yield return null; // nothing
yield return 3; // nothing
yield return new WaitForSeconds(1f); // waits for 1 second
yield return new GameObject(); // nothing
why did you put yield return null
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.
because, remember the job of an enumerator:
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
and, indeed: it's a bit unusual
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.
but couldnt you have just started with yield return 3;
That was just an example of what happens when you yield return different types of objects.
yield return null does the same thing as yield return 3, which is to wait for one frame.
it waits for one frame? i thought it returns a value of 1
in the context of a unity coroutine
alright
by itself, yes, yield return 1; just means that your enumerator's current value becomes 1
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)
sure, but yield return null; is the standard way to indicate "I want this coroutine to wait for one frame"
rather than picking random numbers, or strings, or something
over here it waits a frame, then 3 frames, then waits a second, then
No.
3 is not one of the classes Unity is looking for.
It's looking for things like:
WaitForSecondsWaitForEndOfFrame
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
Unity does not care about that
that just made sense
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.
probably because it'd get ambiguous real fast
so If I had to wait 1 second
yield return 1; i want to wait for one second, not just one frame!
no wait
ah
if I wanted it to instantiate and then wait one second
would I do
Instantiate(gameObject)
yield waitforseconds(1);
yield new WaitForSeconds(1f);, but yes
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
have you walked him through everything, fen?
IEnumerator SpawnCoroutine()
{
while(isSpawning)
{
Instantiate(gameObject)
yield new waitforseconds(0.1f);
isSpawning=false
}
}
he wrote that
This would instantiate an object, wait 0.1 seconds, and then set isSpawning to false
thus exiting the loop
yes, we went through how enumerators work
ah
alright
well THANK YOU GUYS
I HOPE YOU HAVE A GOOD LIFE
A GREAT ONE
I will depart now with my new knowledge
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];
}
i actually have to go for now so ill read this later
this is an IEnumerator that outputs all the entries at the even numbered positions of the list
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
i use IEnumerables more then IEnumerators
yeah, since that lets you use the class with foreach
they are very similar, but Ienumerables are nicer to work with because foreach
well...
to be pedantic, the compiler literally just looks for a GetEnumerator method
it doesn't need the IEnumerable interface
the syntax is nicer
but implementing that interface makes sure that you have the right method
and it lets you manually ask for an enumerator
This thread has turned into an IEnumerator Appreciation meeting and I'm not complaining.
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)
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;
}
explode
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.
okay I got it till ienumerator
ienumerable is for another day perhaps
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.
true
oh wow
alright
well thanks for this masterclass