天天看點

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列印的結果是不同的:

C# 匿名回調方法在循環體中使用的注意事項

如果我們直接在匿名回調方法中使用循環體中的增值變量i,得到的永遠是固定的值,在上面的代碼中也即是ss.Length的值。

然而很多時候我們需要的是當時的循環變量值,雖然在回調方法執行的時候這個循環體早已執行完成,但我們可以通過在循環體内回調方法外單獨存儲一個循環增量i的值,也即是上面的si,這樣在後面的方法回調時便可以按照當時的增量i(也就是si)來取值。

總結就是:

si=循環體循環時增量i的值。

至于這個現象産生的原因,查閱後發現是因為C#背景為我們在回調方法執行之前就提前存儲了該回調方法使用的外部變量。(感覺跟協程的挂起有點像)

也得益于這樣的機制,在一些方法内部書寫回調方法可以使一些複雜的邏輯極快的實作完成,避免了重複的傳遞參數和記錄全局變量。

例如上面的短短幾句話就實作了:

解析選項的文本内容,顯示選項選單,根據選項數量建立對應個數的選項克隆,給克隆的對象添加文本内容和按鈕監聽,當這個按鈕被按下時将選項對應的文本内容輸出到Log中,執行跳轉到選項對應id的文本位置,同時移除所有選項的按鈕監聽,銷毀其餘所有選項,并隐藏選項選單。一個完美的循環!

最重要的是這些隻需要在一個方法中完成,這确實是令人興奮的事。