Lambda表达式作为事件处理程序的变量作用域规则及循环索引越界问题
问题根因
你遇到的索引越界和ListOfA的作用域无关,是C#中for循环变量被Lambda闭包捕获的经典问题:
- 你写的Lambda捕获的是循环变量
i和j本身,不是循环到当前位置时的变量值 - Lambda的变量求值是延迟执行的:只有按钮点击触发事件时,才会读取
i、j的当前值,而不是绑定事件的时候取值 - 双重循环执行完毕后,
i的值已经等于w.ListOfA.Count,j的值也等于最后一轮内层循环的长度,此时触发事件用这两个值索引数组,自然会报越界错误
最佳实践方案
在循环内部声明临时局部变量,存储当前轮次的i、j值,让Lambda捕获这个临时变量即可:每一轮循环的临时变量都是独立的实例,不会被后续循环修改。
修正后的代码如下:
public void loadWorld(World w) { for (int i = 0; i < w.ListOfA.Count; i++) { // 外层循环临时变量存储当前i值 int currentI = i; for (int j = 0; j < w.ListOfA[i].Count; j++) { // 内层循环临时变量存储当前j值 int currentJ = j; Button btn = new Button(); btn.Pressed += (s, e) => handleVisit(w.ListOfA[currentI][currentJ]); } } }
常见疑问解答
- Lambda变量求值时机:所有被闭包捕获的变量,都是在Lambda实际执行的时候才读取当前值,绑定Lambda时不会做值的拷贝。
- 变量生命周期类似智能指针的需求:C#的闭包会自动持有所有捕获的引用类型对象的引用,只要Lambda本身没有被销毁,捕获的对象就不会被GC回收,无需额外做手动生命周期管理,和智能指针的效果一致。你之前的场景问题完全和对象生命周期无关,是循环变量捕获的逻辑错误。
内容的提问来源于stack exchange,提问作者Patsch
相关产品推荐
相关产品推荐

