C# 匿名回调方法在循环体中使用的注意事项
今天在做AVG工具的选择分支功能时发现了一个问题,先把代码贴上来:
1 private void SelectionParse(string value) 2 { 3 string[] ss = value.Split('|'); 4 List<GameObject> s_inss = new List<GameObject>(); 5 6 view.selection.SetActive(true); 7 8 for (int i = 0; i < ss.Length; i++) 9 { 10 string sc = ss[i].Split('@')[0]; 11 string sid = ss[i].Split('@')[1]; 12 13 var ins = Instantiate(view.s_itemPrefab, view.selection.transform); 14 15 s_inss.Add(ins); 16 17 ins.GetComponentInChildren<Text>().text = sc; 18 int si = i; 19 ins.GetComponent<Button>().onClick.AddListener(() => 20 { 21 Debug.Log(i); 22 Debug.Log(si); 23 int id = int.Parse(sid); 24 view.AddLogText("", sc); 25 JumpToIDLine(id); 26 foreach (var s_ins in s_inss) 27 { 28 s_ins.GetComponent<Button>().onClick.RemoveAllListeners(); 29 Destroy(s_ins); 30 } 31 view.selection.SetActive(false); 32 }); 33 } 34 }
上面的代码中,i和si打印的结果是不同的:
如果我们直接在匿名回调方法中使用循环体中的增值变量i,得到的永远是固定的值,在上面的代码中也即是ss.Length的值。
然而很多时候我们需要的是当时的循环变量值,虽然在回调方法执行的时候这个循环体早已执行完成,但我们可以通过在循环体内回调方法外单独存储一个循环增量i的值,也即是上面的si,这样在后面的方法回调时便可以按照当时的增量i(也就是si)来取值。
总结就是:
si=循环体循环时增量i的值。
至于这个现象产生的原因,查阅后发现是因为C#后台为我们在回调方法执行之前就提前存储了该回调方法使用的外部变量。(感觉跟协程的挂起有点像)
也得益于这样的机制,在一些方法内部书写回调方法可以使一些复杂的逻辑极快的实现完成,避免了重复的传递参数和记录全局变量。
例如上面的短短几句话就实现了:
解析选项的文本内容,显示选项选单,根据选项数量创建对应个数的选项克隆,给克隆的对象添加文本内容和按钮监听,当这个按钮被按下时将选项对应的文本内容输出到Log中,执行跳转到选项对应id的文本位置,同时移除所有选项的按钮监听,销毁其余所有选项,并隐藏选项选单。一个完美的循环!
最重要的是这些只需要在一个方法中完成,这确实是令人兴奋的事。