Coroutine and closure issues

I have discovered some interesting behavior regarding the use of closures within coroutines.

It seems that C# closures do not work if I use them within a coroutine, unless the closure is included within a separate function from the coroutine.

In the attached example, three buttons do basically the same thing. Each spawns a list of buttons, and attaches lambda expressions to the spawned buttons that do nothing but log out their (supposedly free) associated index.

[44098-coroutinesclosurestestunitypackage.zip|44098]

Just calling MakeButtons works like it should, with each spawned button logging out 0,1,2,3 depending on which is clicked.

However, calling MakeButtonsCoroutine makes each spawned button act as if it were using an index that is still bound to the calling method’s loop index (just logs out 3).

Through a bit of experimentation, I then discovered that encapsulating the button spawning into a separate method causes the index to once again act like a free variable, with 0,1,2,3 logged out. That is illustrated in MakeButtonsCoroutineSubroutine.

Can someone tell me if this is a unity bug or is there just something I do not understand about the subtleties of coroutines and C# closures?

Here is the test script that contains the bulk of my logic:

using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using System.Collections.Generic;

public class ClosureTest : MonoBehaviour {

	public GameObject madeButtonPrefab;
	public Transform madeButtonParent;
	public int buttonNumber = 4;

	public void MakeButtonsCoroutine()
	{
		StartCoroutine(WaitMakeButtons());
	}

	public void MakeButtonsCoroutineSubroutine()
	{
		StartCoroutine(WaitMakeButtonsSubroutine());
	}

	public void MakeButtons()
	{
		//Clear out all existing buttons first
		foreach (Transform child in madeButtonParent) {
			GameObject.Destroy(child.gameObject);
		}
		
		for(int i = 0; i < buttonNumber; i++)
		{
			GameObject temp = null;
			int captured = i;
			
			temp = Instantiate(madeButtonPrefab, Vector3.zero, Quaternion.identity) as GameObject;
			temp.GetComponentInChildren<Text>().text = "Subroutine Button Log " + captured;
			temp.transform.SetParent(madeButtonParent, false);

			Button madeButton = temp.GetComponentInChildren<Button>();
			
			madeButton.onClick.AddListener(() => {Debug.LogWarning("Captured index: " + captured);});
		}
	}

	IEnumerator WaitMakeButtons()
	{
		yield return new WaitForSeconds(1.0f);
		Debug.Log ("After coroutine");

		//Clear out all existing buttons first
		foreach (Transform child in madeButtonParent) {
			GameObject.Destroy(child.gameObject);
		}
		
		for(int i = 0; i < buttonNumber; i++)
		{
			GameObject temp = null;
			int captured = i;
			
			temp = Instantiate(madeButtonPrefab, Vector3.zero, Quaternion.identity) as GameObject;
			temp.GetComponentInChildren<Text>().text = "No Subroutine Button Log " + captured;
			temp.transform.SetParent(madeButtonParent, false);

			Button madeButton = temp.GetComponentInChildren<Button>();
			
			madeButton.onClick.AddListener(() => {Debug.LogWarning("Captured index: " + captured);});
		}
	}

	IEnumerator WaitMakeButtonsSubroutine()
	{
		yield return new WaitForSeconds(1.0f);
		Debug.Log ("After coroutine call subroutine");
		MakeButtons();
	}
}

This is a unity coroutine implementation issue and I am surprised that nobody even paid attention to this. It is abysmally annoying bug/feature/side effect, whatever you call it.

The problem is this: inside a coroutine, lambda closure inside a for look get the reference of the variables local to the loop scope, while in a normal function, lambda closure gets the copy of the variable. This only happens with Unity coroutine.

I realize that this is much after the original question, but since I just hit exactly this issue myself, I feel the need to weigh in.

This is ABSOLUTELY a bug in Unity. Capturing the variable as you have done should absolutely be sufficient for the closure.

However, I can confirm that for me as well, separating the code requiring the capture into its own method successfully works around the issue.

For various reasons I’m currently still stuck back on Unity 5.2.2. If the coroutine+closures issue has been fixed in a later version and someone’s aware of this, I’d love to hear about it. If it hasn’t been fixed in a newer version, Unity needs to fix this ASAP as right now I’m questioning the wisdom of using Coroutines at all with this kind of unexpected behavior.

@MerlinMobility I used your code in Unity 5.6.6 f2 and both ways led to the same result.It seemed that the ‘Bug’ had been fixed.