C#闭包知识点详解

一闭包的定义

闭包(Closure) 是指一个函数(在 C# 中通常是 Lambda 或匿名方法)与其执行所需的非局部变量环境绑定的实体。

  • "外":外部方法提供的局部变量。

  • "内":内部的 Lambda 表达式。

  • "闭":当 Lambda 被传递到外部方法作用域之外时,它把那些局部变量"封闭"在了自己的包裹里,带着到处跑。

我们可以把这个特性总结为三句话,这三句话能帮你彻底看透闭包的"特权":

1. 访问权:打破"围墙"

正常情况下,函数之间是有"围墙"的。Main 里的变量 moodLevel 本该属于 Main 私有。 但一旦你写了闭包函数(那个 Lambda),它就像在围墙上打了个洞,直接伸手Main 的地盘拿东西用。

2. 携带权:自带"干粮"

这是最神奇的。普通的函数执行完,它所用的局部变量就销毁了。 但闭包函数被赋值给 calculator.CurrentStrategy 后,它就被带到了 PriceCalculator 类里。即便此时 Main 函数已经运行结束了,闭包函数依然随身带着 那个 moodLevel

逻辑: 我虽然离开了家(Main),但我把家里的存折(变量)装进兜里带走了。

3. 修改权:实时更新

闭包函数对外部变量不是简单的"拍照留念",而是**"实时连线"**。

  • 如果你在 Main 里改了 moodLevel = 100

  • 闭包函数下次执行时,拿到的就是 100

  • 反过来,如果闭包函数内部写了 moodLevel--Main 里的 moodLevel 也会跟着变。


⚠️ 必须注意的"代价"

虽然闭包能访问外部变量,但这也是有代价的,你一定要记住这一点:

内存占用(GC 无法回收): 因为闭包函数一直"拽着"外部变量不撒手,只要这个函数(委托对象)还在,那个外部变量就永远不会从内存里消失。

二触发情况

其实,闭包的触发条件非常简单,你可以记一个**"三要素"公式**。只要同时满足这三点,闭包就发生了:

闭包触发"三要素"

  1. 有嵌套:在一个方法(外部)里写了 Lambda 或匿名方法(内部)。

  2. 有引用 :内部方法用到了外部方法的局部变量(包括参数)。

  3. 有传递 :这个内部方法被返回 了,或者传给了其他方法,使得它比外部方法活得更久。

总结:

只要你看到**"逻辑被打包带走"**,就是闭包。

三底层原理

这是 0 基础理解闭包的关键。当你写了一个闭包,编译器会执行**"提升"(Hoisting)**操作:

  1. 生成隐藏类:编译器偷偷创建一个类(程序员看不见)。

  2. 搬家 :把被捕获的局部变量,从**栈(Stack)上搬到这个秘密类的字段(Field)**里。

  3. 实例化:在方法运行时,实例化这个类。由于类在**堆(Heap)**上,所以即便方法结束了,变量依然活着。

四必考知识点:捕获的是"变量"而非"值

这是闭包最著名的坑,我们通过代码来拆解:

错误示范(著名的 For 循环坑)

cs 复制代码
List<Action> actions = new List<Action>();

for (int i = 0; i < 3; i++)
{
    // 每一个 Action 都捕获了变量 i
    actions.Add(() => Console.WriteLine(i));
}

foreach (var a in actions) a();

结果: 打印出 3, 3, 3,而不是 0, 1, 2原因: 三个 Lambda 捕获的是同一个变量 i 的引用。当循环结束时,i 的值变成了 3,所以大家执行时看到的都是 3。

正确做法(创建副本)

cs 复制代码
for (int i = 0; i < 3; i++)
{
    int temp = i; // 每次循环都创建一个新的局部变量
    actions.Add(() => Console.WriteLine(temp));
}

结果: 0, 1, 2。因为每次循环的 temp 都是独立的变量,被不同的闭包实例分别捕获。

五闭包的优缺点分析

✅ 优点

  • 数据持久化:不需要定义全局变量,就能在多次调用间保持状态。

  • 简化逻辑:在回调函数中直接使用当前环境的数据,不需要通过参数传来传去。

  • 灵活架构:是实现"柯里化(Currying)"和"工厂模式"的基础。

❌ 缺点(风险)

  • 内存泄漏 :如果闭包捕获了一个巨大的对象(如 List),且委托一直不销毁,垃圾回收器(GC)就永远无法回收那个大对象。

  • 副作用:多个闭包修改同一个变量时,会产生难以调试的状态同步问题。

六什么时候用闭包

  • 动态配置逻辑:如练习 3 的问候器,根据初始参数生成不同的逻辑。

  • 异步编程 :在 TaskThread 中记住启动时的上下文。

  • LINQ 查询 :在 .Where(x => x > limit) 中,limit 往往是一个外部变量。

相关推荐
傻啦嘿哟7 小时前
某招聘平台爬虫:爬取招聘岗位数据,分析各城市薪资水平
开发语言·爬虫·python
2501_933670797 小时前
2026秋招量化分析岗技能栈:Python、SQL、统计建模、回测项目怎么准备
开发语言·python·sql
李少兄7 小时前
JavaScript 数据类型完全指南
开发语言·javascript·ecmascript
Seoyoneh8 小时前
Agentic Workflow编排架构:云客服从“被动响应”迈向“主动执行”的技术实现
java·开发语言·架构
Tangyuewei8 小时前
Java 写 Agent:模型只是组件
java·开发语言
小玮看世界9 小时前
[Python]从合并区间到传感器融合区:合并区间在传感器区域融合的实际落地
开发语言·python
繁星蓝雨10 小时前
C++的设计与演进大纲(如何从C语言一步步成长为C++)
c语言·开发语言·c++·c++历史·c++演进
Evand J10 小时前
【MATLAB例程,图像降噪滤波9】自适应维纳滑动窗口滤波(Wiener)图像降噪、方法对比,有中文注释
开发语言·图像处理·计算机视觉·matlab·滑动窗口·滤波·降噪
杜大哥10 小时前
python程序:如何查看电脑【电池电量的剩余百分比】 和 【是否插入连接着充电器】?
开发语言·python
wuyk55510 小时前
Python网络爬虫入门到实战 第01章:爬虫到底是什么?原理、流程、合法性、风险全解析(零基础必看)
开发语言·爬虫·python