Rita 如果在 for 循环内部定义 lambda 表达式,并且这个 lambda 捕获了循环变量,那么每个 lambda 都会形成一个闭包,闭包中包含了被捕获的变量的引用。由于这些闭包引用了外部变量,因此这些变量不会被垃圾回收(Garbage Collection, GC),即使它们已经超出了其作用域。
一个简单的例子:
List<Action> actions = new List<Action>();
for (int i = 0; i < 1000; i++)
{
actions.Add(() => Console.WriteLine(i)); // 这里捕获了变量 i
}
foreach (var action in actions)
{
action(); // 在 C# 5.0 及之前的版本中,这将会打印出 1000 十次
}
在 C# 5.0 及之前的版本中,上述代码中的 lambda 表达式会捕获同一个变量 i,因此当 for 循环结束时,i 的值会是 1000,所以所有的 lambda 表达式都会打印出 1000。然而,在 C# 6.0 及更高版本中,循环中的 lambda 捕获的是每次迭代时 i 的值的一个快照,因此每个 lambda 都会打印出不同的值。
尽管如此,在任何版本的 C# 中,如果 lambda 表达式被长期保存并在之后执行,它们都会形成闭包并持有对外部变量的引用,这可能会阻止这些变量被垃圾回收。如果这样的闭包被大量创建并且长时间存在,那么它们确实会占用内存并可能导致内存使用量逐渐增加。
为了避免这种情况,可以使用一个局部变量来创建闭包的独立实例,如下所示:
List<Action> actions = new List<Action>();
for (int i = 0; i < 1000; i++)
{
int copy = i; // 创建一个局部变量的拷贝
actions.Add(() => Console.WriteLine(copy)); // 现在每个 lambda 捕获的是不同的 copy 变量
}
foreach (var action in actions)
{
action(); // 这将按顺序打印出 0 到 999
}
在这个修改后的代码中,每个 lambda 表达式都捕获了一个独立的 copy 变量,这样就不会再有内存泄漏的问题了,因为每个 lambda 都有自己的数据拷贝,而不是共享同一个变量。