Untiy 当以遍历的方法给多个按钮添加事件 并且把i当参数传入时报错

本文主要针对的问题是在Unity中对Button类进行Onclick事件绑定的时候出现的函数参数错误进行分析解决,具体问题如下:

Button[] button = GetComponentsInChildren();
int buttonCnt = 3;

for (int i = 0; i < buttonCnt; i++)
{

Debug.Log("i: " + i);
button[i].OnClick.AddListener( () => ClickButton(i) );

}

public void ClickButton(int addScore)
{

Debug.Log("addScore: " + addScore);
score = score + addScore;

}
这段代码中主要对buttonCnt个按钮绑定Onclick事件,期望用户在点击不同按钮的时候能给score加上不同的分数,但是实际上不管在点击任何按钮以后都会得到如下输出:

i: 0
i: 1
i: 2
addscore: 3
与我们的预期不符,我们期待在按不同按钮的时候能得到按钮的id:i,但是实际上我们不管按哪个按钮都会得到一个奇怪的数字3。如果我们仔细观察,可以注意到3恰好是遍历结束以后i的值,那这其中到底发生了什么呢?

原因
出现上述问题的原因主要与闭包(Closure)的特性有关。闭包的定义如下

闭包是一个绑定到声明它的环境中的函数。因此,该函数可以在其函数体中引用环境中的元素。换句话说,闭包是指有权访问另一个函数作用域中的变量的函数

上一段代码实际上是声明了一个匿名方法,再把这个匿名方法作为Onclick的事件。而匿名方法是一个闭包,并且绑定到它的父方法体和其中的局部变量上。其中我们需要注意的是:匿名方法绑定到变量上,而不是值。换句话说,当“ClickButton”被声明时,“i”的值没有被复制进来。相反,匿名方法将使用对“i”的引用,这样“ClickButton”将始终使用“i”的最新值。因此在给按钮进行事件绑定以后,不管用户对哪个按钮进行点击,都会调用"i"的最新值,也就是3。事实上,即使“i”超出了作用域,对“i”的引用也将持续发挥作用,如以下例子:

delegate void Action();

static Action GetAction()
{

int i = 0;

Action a = delegate {
Console.WriteLine(i); };

i = 1;

return a;
}

static void Main(string[] args)
{

Action a = GetAction();

a();
}
尽管在"a"被调用的位置已经不在局部变量"i"的作用域,但是输出仍然是1而不是0

解决方案
在了解了闭包的特性以后要解决这个问题就变得很简单了,只需要保证在给ClickButton传参的时候传入一个新的变量,而不是一个持续在使用的变量就可以了,因此可以将

button[i].OnClick.AddListener(delegate {
() => ClickButton(i); });
修改为

int temp = i
button[i].OnClick.AddListener(delegate {
() => ClickButton(temp); });
这样每次传入ClickButton的都是一个重新声明的局部变量,可以很好地解决这个问题

转载https://cxyzjd.com/article/shanwenkang/116739829

猜你喜欢

转载自blog.csdn.net/o_ojjj/article/details/120348056