闭包捕获问题(Closure Capture Issue)是编程中一个常见的陷阱,尤其在循环内创建委托(如Lambda表达式)时容易遇到。在C#中,闭包(Closure)会捕获变量本身,而不是变量的值,这可能导致循环中的多个委托共享同一个变量,最终得到意外的结果。
例子
for (int i = 0; i < 10; i++)
{
// 直接捕获循环变量i
btn[i].onClick.AddListener(() => AddDigit(i.ToString()));
}
这段代码有个问题,无论点击哪个按钮,最终传入 AddDigit
的值都会是 10
(循环结束时 i
的值),而不是按钮对应的 0-9
。
问题产生的原因
-
闭包捕获的是变量,不是值
- Lambda表达式
() => AddDigit(i.ToString())
中的i
是循环变量,而不是循环迭代时的值。 - 所有按钮的点击事件都会共享同一个变量
i
。
- Lambda表达式
-
循环结束后变量
i
的值是10
- 当循环结束时,
i
的值是10
(因为循环条件是i < 10
,最后一次循环后i++
使其变成10
)。 - 用户点击按钮时,事件触发的是当前
i
的值,也就是10
。
- 当循环结束时,
解决方法:
for (int i = 0; i < 10; i++)
{
int num = i; // 每次循环都创建一个新的num
btn[i].onClick.AddListener(() => AddDigit(num.ToString()));
}
其它闭包捕获问题的例子
for (int i = 0; i < 3; i++)
{
// 直接捕获i
Task.Run(() => Console.WriteLine(i));
}
输出结果可能是 3, 3, 3
,而不是预期的 0, 1, 2
。