全网最通透的“闭包”认知 · 跨越语言

简介: 今天我们深入聊一聊[闭包], 查缺补漏! 1. 以面试题 · 投石问路 2. 以C#闭包 · 庖丁解牛 3. 跨越语言 ·追本溯源 • 头等函数 •自由变量 •词法作用域4. 答面试题 · 返璞归真

1. 投石问路


调用下面函数,输出结果是什么样呢?


static void Closure1(){   for (int i = 0; i < 5; i++)   {                      Task.Run(()=> Console.WriteLine(i));   }}//  输出:55555


是不是很意外?如何输出原本预期的 0,1,2,3,4。


bingo, 加一个临时变量就可以解决。


static void Closure2(){   for (int i = 0; i < 5; i++)   {      int j = i;      Task.Run(() => Console.WriteLine(j));   }}// 输出:30142//  多次执行的结果不一样,但是总是会保持输出 0,1,2,3,4 的乱序组合


以上闭包概念涉及到 Task任务,理解起来更加复杂,我们来看一个基础的C#闭包。


2. 庖丁解牛


一个闭包就是一个“捕获”了其生成的环境中、所引用的自由变量的函数。


这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。


static void Closure() {    var x = 1;    Action action= () =>      {         var y = 1;         var result = x + y;         Console.WriteLine(result);         x++;      };    action();    action();}   // 输出:  2  3


我们首先定义了一个委托action,它引用了“x”变量(x变量既不是入参,也不是委托内的局部变量), 这个变量将被action"捕获”,被自动添加到action 的运行环境。


当我们执行action时,原始的“x”已经脱离了它被引用时的作用域环境,但是两次执行能输出2,3 说明它脱离原引用环境仍然能用。


当你在代码调试器(debugger)里观察“action”时,可以看到C#编译器为我们创建了一个Target属性,里面封装了 x 变量:


9c25150dd79c102b29c8fb4f51a248f2.png


源码追溯,委托继承自Delegate抽象类,Delegate类有个Target 属性(获取当前委托调用实例方法的实例类) 。

至此可以猜想: 我们每次执行委托,实际是是执行某个匿名类上的实例方法。


都说了闭包是跨越语言的设计, 至少我知道 JavaScript C# Go都有闭包。


3. 追本溯源


闭包是词法闭包的简称,维基百科上是这样定义的:


在计算机编程中,闭包是在词法环境中绑定自由变量的头等函数”。


头等函数


头等函数( First Class)意味着语言将其视为第一类数据类型的函数, 意味着你可以将函数分配给一个变量(或作为参数传递),然后像正常函数一样调用。


很明显,C#常使用的委托(C#委托的演进:匿名函数-->lambda表达式)是头等函数。


Func<string,string> myFunc = delegate(string var1)                                {                                    return "some value";                                   };Func<string,string> myFunc = var1 => "some value";  string myVar = myFunc("something");


自由变量


自由变量是在匿名函数/lambda表达式中被引用的变量,它不是函数的参数也不是函数的局部变量。


var myVar = "this is good";Func<string,string> myFunc = delegate(string var1)                                {                                    return var1 + myVar;                                   };


词法作用域引用的自由变量,注意,是引用自由变量,并不是使用当时自由变量的值


☺️通俗点, 就是告知这个变量环境,我这个匿名函数等会执行时要用到这个变量;如果我没被销毁,你不能销毁我引用的自由变量。


我们再回过头来看[投石问路]的面试题。


4. 返璞归真


首先你要知道:循环内开启的Task任务,并不保证执行顺序。


Demo1:输出5,5,5,5,5


这是因为在 for循环内,开启了5个Task任务,每个任务均引用了自由变量i (相对于每个任务执行环境,i 属于全局变量);


for循环先执行完,i=5, 5个任务输出时自然得到值5。


d04fd11d8267184497f3a638cd87e20a.jpg


为什么加上临时变量就能输出"预期"?


Demo2:输出乱序的0,1,2,3,4


这是因为 在for循环内,每次循环j均拷贝自当时的i,每个任务均引用了自由变量 j (每个任务执行环境均维护了一个变量j);


任务乱序执行时依旧能获取本任务绑定的自由变量j。


ea3e41efba0bcf113c92a17a5cfc9b7a.jpg


有这样的认知,理解JavaScript 闭包也就不难了。


094a27d507985df12df9fded0826671c.png


# 总结


本文屏蔽语言差异,理清了[闭包]的概念核心: 头等函数、自由变量,不仅能帮助我们应对多语种有关闭包的面试题, 也帮助我们了解[闭包]在通用语言中的设计初衷。


相关文章
|
弹性计算 图形学
Unity之浅析 Entity Component System (ECS)
首先放出ECS官方文档 随着目前游戏对CPU性能要求的不断提升,单核高频的CPU对我们的帮助越来越有限。所以ECS(一种面向数据编程)多核心工作的方式也是大势所趋。
3879 0
|
11月前
|
缓存 C# Windows
C#程序如何编译成Native代码
【10月更文挑战第15天】在C#中,可以通过.NET Native和第三方工具(如Ngen.exe)将程序编译成Native代码,以提升性能和启动速度。.NET Native适用于UWP应用,而Ngen.exe则通过预编译托管程序集为本地机器代码来加速启动。不过,这些方法也可能增加编译时间和部署复杂度。
590 2
|
消息中间件 网络协议 算法
UDP 和 TCP 哪个更好?
【8月更文挑战第23天】
577 0
|
5月前
|
人工智能 自然语言处理 运维
让搜索引擎“更懂你”:AI × Elasticsearch MCP Server 开源实战
本文介绍基于Model Context Protocol (MCP)标准的Elasticsearch MCP Server,它为AI助手(如Claude、Cursor等)提供与Elasticsearch数据源交互的能力。文章涵盖MCP概念、Elasticsearch MCP Server的功能特性及实际应用场景,例如数据探索、开发辅助。通过自然语言处理,用户无需掌握复杂查询语法即可操作Elasticsearch,显著降低使用门槛并提升效率。项目开源地址:&lt;https://github.com/awesimon/elasticsearch-mcp&gt;,欢迎体验与反馈。
1401 1
Java系列之 超时任务处理方法
这篇文章介绍了Java中处理超时任务的方法,通过使用`FutureTask`和`ExecutorService`来异步执行可能耗时的任务,并设置超时时间,如果任务在指定时间内未完成,则主动结束任务并返回默认结果。
Java系列之 超时任务处理方法
|
缓存 算法 Java
底层原理:垃圾回收算法是如何设计的?
理解Java虚拟机垃圾回收机制的底层原理,是成为一个高级Java开发者的基本功。本文从底层的垃圾回收算法开始,着重去阐释不同垃圾回收器在算法设计和实现时的一些技术细节,去探索「why」这一部分,通过对比不同的垃圾回收算法和其实现,进一步感知目前垃圾回收的发展脉络。
14948 2
底层原理:垃圾回收算法是如何设计的?
|
11月前
|
存储 算法 搜索推荐
数据结构--堆的深度解析
数据结构--堆的深度解析
|
Java API 数据库
如何在Java中使用GraphQL
如何在Java中使用GraphQL
|
Ubuntu Python
百度搜索:蓝易云【如何在 Ubuntu 22.04 上安装 Python Pip?】
现在你已经成功在 Ubuntu 22.04 上安装了 Python Pip。你可以使用 Pip 来安装各种 Python 包和库,以满足你的开发需求。
340 1
|
JavaScript 前端开发 搜索推荐
服务器端渲染技术SSR与ISR:深入解析与应用
【7月更文挑战第20天】服务器端渲染(SSR)和增量静态再生(ISR)作为现代Web开发中的两种重要渲染技术,各有其独特的优势和适用场景。在实际应用中,开发者应根据具体需求和条件选择合适的渲染模式。无论是追求极致的页面加载速度和SEO优化,还是实现内容的实时更新,SSR和ISR都能提供有效的解决方案。通过深入理解这些技术的工作原理和应用场景,开发者可以构建出更加高效、可靠和用户体验优异的Web应用。