The Trouble with For Loops

There is an unpatched bug in the mono compiler that Unity runs on. It only shows itself when you try to reference the variable that you are iterating through in a for loop or a foreach loop, and when you are creating a closure in a lambda expression.

Lets see an example. Neither the for or the foreach blocks will execute like you expect them to:

public Transform[] Waypoints;

Effect<Controller, Vector3> effect = new Effect<Controller, Vector3>();
[ ... ] // populate the fields of the effect.
Sequence<Controller, Vector3> sequence = new Sequence<Controller, Vector3>();

sequence = effect * Waypoints.Length;

for (int i = 0; i < sequence.Effects.Length; i++)
{
    sequence.Effects[i].RetrieveEnd = system => Waypoints[i].position;
}

int i = 0;
foreach(Transform waypoint in Waypoints)
{
    sequence.Effects[i++].RetrieveEnd = system => waypoint.position;
}

 
Both loops are creating a closure. In the for loop “Waypoints[i]” creates a closure, and in the foreach loop “waypoint” creates a closure. A closure is ok to use in the RetrieveEnd field, since that is only executed once per effect. The trouble is that in both of these cases the item that ends up getting used is not the current item that you passed in, but instead every item will be set to the position of the last item in the list.

Fortunately, this bug only affects variables that are declared at the top of a for or foreach loop, so if you create the variable in any other way it will work fine. Here is one way that you can work around it:

for (int i = 0; i < sequence.Effects.Length; i++)
{
    int localI = i;
    sequence.Effects[i].RetrieveEnd = system => Waypoints[localI].position;
}

foreach(Transform waypoint in Waypoints)
{
    Transform localCopy = waypoint;
    sequence.Effects[i++].RetrieveEnd = system => localCopy .position;
}

 
Another way to avoid the bug is to convert your for loop into a while loop, like this:

int i = 0;
while(i < sequence.Effects.Length)
{
    sequence.Effects[i].RetrieveEnd = system => Waypoints[i].position;
    i++;
}