Unity中按钮(Button)控件Onclick事件函数参数错误 —— C#中的闭包(Closure)

问题

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

Button[] button = GetComponentsInChildren<Button>();
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的都是一个重新声明的局部变量,可以很好地解决这个问题

参考资料

1.https://stackoverflow.com/questions/40183703/c-sharp-unity-wrong-argument-of-the-function-in-onclick-event
2.https://web.archive.org/web/20150707082707/http://diditwith.net/PermaLink,guid,235646ae-3476-4893-899d-105e4d48c25b.aspx

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值