1. 闭包陷阱:C#开发者的常见痛点
在C#开发中,闭包是一个强大但容易踩坑的特性。特别是当它与foreach循环结合使用时,会产生一些反直觉的行为。很多开发者第一次遇到这个问题时都会感到困惑:明明代码逻辑看起来没问题,为什么运行结果却不符合预期?
这个问题在异步编程、多线程场景中尤为突出。我曾在实际项目中遇到过这样一个案例:在一个并行处理任务中,使用foreach循环创建了多个异步操作,结果发现所有操作都使用了循环中最后一个元素的值。经过排查,才发现是闭包捕获变量的机制导致的。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. foreach循环中的闭包陷阱解析
2.1 闭包的基本概念
闭包是指一个函数(或匿名方法/lambda表达式)能够访问并记住其词法作用域中的变量,即使该函数在其原始作用域之外执行。在C#中,闭包通过捕获外部变量来实现这一特性。
csharp复制void Example()
{
int x = 10;
Action action = () => Console.WriteLine(x);
x = 20;
action(); // 输出20,而不是10
}
在这个简单例子中,lambda表达式捕获了变量x,即使x的值在创建lambda后被修改,闭包仍然会"看到"最新的值。
2.2 foreach循环的特殊行为
foreach循环在C#中的实现方式与闭包交互时会产生一些特殊行为。考虑以下代码:
csharp复制var actions = new List<Action>();
foreach (var i in Enumerable.Range(0, 3))
{
actions.Add(() => Console.WriteLine(i));
}
foreach (var action in actions)
{
action();
}
你可能会预期输出0,1,2,但实际上会输出3,3,3。这是因为在C# 5.0之前,foreach循环中的迭代变量(本例中的i)是在循环外部声明的,所有闭包都捕获同一个变量。
2.3 编译器如何处理foreach循环
为了理解这个问题,我们需要看看编译器是如何处理foreach循环的。在C# 5.0之前,上述代码大致会被编译
