1. 从一段诡异代码说起:为什么foreach和for循环表现不同?
让我们先看一个让很多C#开发者栽过跟头的经典例子:
csharp复制var actionsFor = new List<Action>();
var actionsForEach = new List<Action>();
// for循环版本
for (int i = 0; i < 5; i++)
{
actionsFor.Add(() => Console.WriteLine(i));
}
// foreach循环版本
foreach (var j in Enumerable.Range(0, 5))
{
actionsForEach.Add(() => Console.WriteLine(j));
}
// 执行所有Action
Console.WriteLine("for循环结果:");
foreach (var action in actionsFor) action();
Console.WriteLine("foreach循环结果:");
foreach (var action in actionsForEach) action();
运行这段代码,你会看到令人困惑的输出:
code复制for循环结果:
5
5
5
5
5
foreach循环结果:
0
1
2
3
4
这个现象我第一次遇到时也百思不得其解——明明看起来相似的循环结构,为什么闭包行为完全不同?这背后隐藏着C#语言设计的重要细节。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 闭包的本质:捕获的是引用而非值
要理解这个现象,首先需要明确闭包(closure)的核心机制:
闭包是在词法环境中捕获自由变量的函数,它捕获的是变量的引用而非变量值的快照。
在C#中,当lambda表达式或匿名方法引用外部变量时,就形成了闭包。关键点在于:
- 闭包捕获的是变量本身,而非变量当前的值
- 被捕获变量的生命周期会被延长至与闭包相同
- 多个闭包可以捕获同一个变量
这就解释了for循环版本的行为:所有闭包都捕获了同一个变量i,当循环结束后i的值变为5,所以所有闭包执行时输出的都是5。
3. foreach的魔法:每次迭代都是新的局部变量
foreach的行为看起来更符合直觉,这是因为
