Wednesday, November 4, 2015

Yield bogeyman 1/2

When I first saw yield keyword in Unity I did not understand how it works. It looked like using yield keyword method could preserve its state between calls, which is something I was not able to find some parallel in C or C++. I started searching MSDN and other sites to find what the story behind this weird thing is. Below is what I found.

Let's start with the foreach loop. It could be used for looping over elements in classes, which implements IEnumerable interface (Let's save us now some time and let's not consider the generic IEnumerable<T> interface because I was using generics more than 10 years ago in C++ and to be honest I am not 100% sure how to use them correctly).

IEnumerable is an interface, which has only one method GetEnumerator() to be implemented. This method returns the IEnumerator interface. IEnumerator methods are then used to iterate through the collection. So if we want to create a custom collection, which could be used in forech loop, we have to implement both interfaces. This is the situation, where yield keyword will help us: As shown in the example below using yield removes the need for an explicit extra class IEnumerator when you implement the IEnumerable pattern for a custom collection. Example shows two implementations of the same task – one implementation uses IEnumerator, second uses the yield keyword:
Car class:
public class Car
{
 private string manufacturer;
 public string Manufacturer
 {
  get { return manufacturer; }
  set { manufacturer = value; }
 }
 
 private string model;
 public string Model
 {
  get { return model; }
  set { model = value; }
 }
 
 private int year;
 public int Year
 {                        
  get { return year; }
  set { if (year > 1900 ) year = value; }
 }
 
 public Car ( string manufacturer, string model, int year )
 {
  Manufacturer = manufacturer;
  Model = model;
  Year = year;
 }
}

Garage class with IEnumerator interface implementation:
class Garage : IEnumerable, IEnumerator
{
 private Car[] cars;
 private int index = -1;

 public Garage()
 {
  cars = new Car[5]{ 
   new Car( "Mercedes", "190E", 1991 ),
   new Car( "Fiat", "Uno", 1983 ),
   new Car( "Ford", "Escord", 1987 ),
   new Car( "Lada", "Samara", 1993 ),
   new Car( "Renault", "5", 1985 ) };
 }

 IEnumerator IEnumerable.GetEnumerator()
 {
  return this;
 }
 
 public bool MoveNext()
 {
  index++;
  return ( index < cars.Length);
 }
 
 public void Reset()
 {
  index = 0;
 }
 
 public object Current
 {
  get { return cars[index]; }
 }
}

Garage class with use of the yield keyword:
class GarageYield : IEnumerable
{
 private Car[] cars;

 public GarageYield()
 {
  cars = new Car[5]{ 
   new Car( "Mercedes", "190E", 1991 ),
   new Car( "Fiat", "Uno", 1983 ),
   new Car( "Ford", "Escord", 1987 ),
   new Car( "Lada", "Samara", 1993 ),
   new Car( "Renault", "5", 1985 ) };
 }
 
 IEnumerator IEnumerable.GetEnumerator()
 {
  foreach ( Car c in cars )
  {
   yield return c;
  }
 }
}

Loop:
void Awake () 
{

 Garage garage = new Garage();
 foreach ( Car c in garage )
 {
  Debug.Log( "[CAR]" + c.Manufacturer + " " + c.Model );
 }

 GarageYield garageYield = new GarageYield();
 foreach ( Car c in garageYield )
 {
  Debug.Log( "[CAR_YIELD]" + c.Manufacturer + " " + c.Model );
 }
}

Output:
[CAR]Fiat Uno
[CAR]Ford Escord
[CAR]Lada Samara
[CAR]Renault 5
[CAR_YIELD]Mercedes 190E
[CAR_YIELD]Fiat Uno
[CAR_YIELD]Ford Escord
[CAR_YIELD]Lada Samara
[CAR_YIELD]Renault 5
It works in a way that when a foreach loop is called and the yield return statement is reached, the current location in code is retained. Next time execution is restarted from that location. See one more example:
IEnumerable YieldTest()
{
 yield return 1;
 yield return 2;
 yield return 3;
}

void Awake () 
{
 foreach (int i in YieldTest())
 {
  Debug.Log( "[YIELD] Value: " + i );
 }
}

Output: 
[YIELD] Value: 1
[YIELD] Value: 2
[YIELD] Value: 3

Yield in Unity

Now, when we know how the yield works - how it is used in Unity?

In Unity method, which uses yield keyword is called coroutine - this I describe in the next part of the blog - after I understand it:)

No comments:

Post a Comment