1. 闭包陷阱:为什么foreach循环会坑到你?
在C#开发中,闭包是一个强大但容易踩坑的特性。特别是当它与foreach循环结合使用时,会产生一些反直觉的行为。我们先来看一个经典案例:
csharp复制var actions = new List<Action>();
foreach (var i in Enumerable.Range(1, 3))
{
actions.Add(() => Console.WriteLine(i));
}
foreach (var action in actions)
{
action();
}
你期望的输出是什么?1,2,3?实际上会输出3,3,3!这就是著名的foreach闭包陷阱。
1.1 闭包捕获机制解析
闭包捕获的是变量而非值。在C#中,foreach循环中的迭代变量(上例中的i)在循环体外部声明,然后在每次迭代中重用。这意味着:
- 所有lambda表达式捕获的是同一个i变量
- 当lambda执行时,i的值已经是循环结束后的最终值
- 这就是为什么所有委托都输出相同值
重要提示:这个行为在C# 5.0及以后版本只针对foreach循环。for循环的行为是不同的,因为for循环的迭代变量在每次迭代中都是独立的。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 问题本质与编译器行为
2.1 编译器生成的IL代码分析
让我们看看编译器如何处理上述foreach循环。经过反编译,你会发现:
csharp复制List<Action> actions = new List<Action>();
IEnumerator<int> enumerator = Enumerable.Range(1, 3).GetEnumerator();
try
{
while (enumerator.MoveNext())
{
int i = enumerator.Current; // 注意这里
actions.Add(() => Console.WriteLine(i));
}
}
finally
{
if (enumerator != null)
{
enumerator.Dispose();
